Skip to content
Snippets Groups Projects
SOMDocToLambdaExpression.cs 50.5 KiB
Newer Older
MaZiFAU's avatar
MaZiFAU committed
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
MaZiFAU's avatar
MaZiFAU committed
using System.Linq.Expressions;
using System.Reflection;
MaZiFAU's avatar
MaZiFAU committed
using UnityEngine;

namespace REST_JSON_API
{
    abstract public partial class SOMDoc
    {
        /// <summary>
        /// translates this SOMDoc AST into a C\# AST, changes/casts the signature to Func<object[], object[]>, and compiles it.
        /// Function arguments can be inserted at any position as to partially invoke the function. 
        /// </summary>
        /// <param name="compile_base">Cached Expression Tree</param>
        /// <param name="signature">the original signature</param>
        /// <param name="callArgs">arguments to be inserted. The position carries over. Set <c>null</c> to skipp all.</param>
        /// <param name="useArgs">set a position to <c>false</c> iff the corresponding entree in <paramref name="callArgs"/> shall be ignored / the original parameter be used. 
        /// Missing values will be interpreted as <c>true</c>. Set <c>null</c> to use all.</param>
        /// <returns>the compiled function, with a new signature and partially inserted arguments.</returns>
        public Func<object[], object[]> PartialInvokeCastingLambdaExpression(out Expression compile_base, out Type[] signature, object[] callArgs = null, bool[] useArgs = null)
MaZiFAU's avatar
MaZiFAU committed
        {
            List<Type> signature_list;
            LambdaExpression lambda_orig = GetLambdaExpression();

            if (FuncExtensions.IsFuncType(lambda_orig.ReturnType, out int signature_count))
            {
                signature_list = lambda_orig.ReturnType.GetGenericArguments().ToList();
MaZiFAU's avatar
MaZiFAU committed
            }
            else
            {
                signature_count = lambda_orig.Parameters.Count + 1;
                signature_list =
                    Enumerable.Append(
                        lambda_orig.Parameters.Select(p => p.Type),
                        lambda_orig.ReturnType)
                    .ToList();
MaZiFAU's avatar
MaZiFAU committed
            }

            ParameterExpression object_arr = Expression.Parameter(typeof(object[]), "PARAMS_Arr");
            Expression[] cast_new_to_signature = new Expression[signature_count - 1];

            List<int> removed = new();
            for (int i = 0; i < signature_count - 1; i++)
            {
                if (callArgs != null && callArgs.Length < i
                 && (useArgs == null || (useArgs.Length < i && useArgs[i])))
                {
                    cast_new_to_signature[i] =
                        Expression.Constant(callArgs[i], signature_list[i]);

                    removed.Add(i);
                    continue;
                }

                cast_new_to_signature[i] =
                    Expression.Convert(
                        Expression.ArrayIndex(
                            object_arr,
                            Expression.Constant(i)
                        ),
                        signature_list[i]
                    );
            }
            signature = signature_list.SkipAt(removed).ToArray();

            LambdaExpression final_expression =
                Expression.Lambda(
                    typeof(object[]).IsAssignableFrom(signature[^1])
                    ? Expression.Invoke(compile_base, cast_new_to_signature)
MaZiFAU's avatar
MaZiFAU committed
                    : Expression.NewArrayInit(
                        typeof(object),
                        new Expression[] { Expression.Convert(Expression.Invoke(compile_base, cast_new_to_signature), typeof(object)) }),
MaZiFAU's avatar
MaZiFAU committed

                    object_arr
                );

            return final_expression.Compile() as Func<object[], object[]>;
        }

        /// <summary>
        /// Libary and configuartions to translate a SOMDoc AST into an Expression-Tree
        /// </summary>
MaZiFAU's avatar
MaZiFAU committed
        protected static class SOMDocToLambdaExpression
        {
            // TODO: Populate Dictionaries
            #region ExpressionDictionaries

            /// <summary>
            /// Builds a <see cref="LambdaExpression"/> within <see cref="MakeLambdaExpression"/>
            /// </summary>
            /// <param name="lambda_applicant">arguments to be used; usually comes from <see cref="OMA.arguments"/></param>
            /// <param name="lambda_arguments">currently unused. space for a second set of parameters</param>
            /// <param name="bound_params">to be bound as parameters in <see cref="Expression.Lambda(Expression, ParameterExpression[])"/></param>
            /// <returns>encoding the current Abstract-Syntax-Tree</returns>
MaZiFAU's avatar
MaZiFAU committed
            public delegate LambdaExpression CustomFunction(LambdaExpression[] lambda_applicant, LambdaExpression[] lambda_arguments, ParameterExpression[] bound_params);
            private static readonly Dictionary<string, CustomFunction> MMTtoLambdaMaker = new()
        {
            { MMTConstants.Sin,
                MakeSin },
            { MMTConstants.Cos,
                MakeCos },
            { MMTConstants.SquareRoot,
MaZiFAU's avatar
MaZiFAU committed
                MakeRoot },
            { MMTConstants.Tuple,
                MakeTupel },
            { MMTConstants.Product, // TODO:Automate
                MakeTupel },
            { MMTConstants.Angle, // TODO: get AST from server?
                CalculateAngle },
            { MMTConstants.MakeObjectArray,
                MakeObjArray },
                MakeInstantList },
            { MMTConstants.ListEnd,
                MakeListEnd },
            { MMTConstants.ListLiteral,
                InsertFrontListLiteral },
            { MMTConstants.ListType,
                Identity0 },
            { MMTConstants.ScalarProduct,
                CallAnyFunction(false, "Dot", typeof(Vector3)) },
            { MMTConstants.VecCross,
                CallAnyFunction(false, "Cross", typeof(Vector3)) },
            //{ MMTConstants.VecMultI,
            //    CallAnyFunction(false, "Scale", typeof(Vector3)) },
            { MMTConstants.ProjL,
            { MMTConstants.ProjR,
                ProjRVecTupel },
            { MMTConstants.Map,
                ChainMakes(new[]{
                    CallAnyFunction(false, "Select", typeof(Enumerable)),
                    CallAnyFunction(false, "ToList", typeof(Enumerable))})},
            { MMTConstants.FeedForwardWhile,
MaZiFAU's avatar
MaZiFAU committed
                FeedForwardUntil },
            { MMTConstants.FeedForwardWhileT2,
                FeedForwardUntil },
MaZiFAU's avatar
MaZiFAU committed
            { MMTConstants.FeedForwardWhile2,
                FeedForwardUntil },
            { MMTConstants.PartialAggregate,
                CallAnyFunction(false, "PartialAggregate", typeof(IEnumerableExtensions)) },
            { MMTConstants.ToArray,
                CallAnyFunction(false, "ToArray", typeof(Enumerable)) },
            { MMTConstants.Filter,
                ChainMakes(new[]{
                    CallAnyFunction(false, "Where", typeof(Enumerable)),
                    CallAnyFunction(false, "ToList", typeof(Enumerable))})},
MaZiFAU's avatar
MaZiFAU committed
            { MMTConstants.Filter2,
                ChainMakes(new[]{
                    CallAnyFunction(false, "Where", typeof(Enumerable)),
MaZiFAU's avatar
MaZiFAU committed
                    CallAnyFunction(false, "ToList", typeof(Enumerable))})},
            { MMTConstants.FilterT2,
                ChainMakes(new[]{
                    CallAnyFunction(false, "Where", typeof(Enumerable)),
                    CallAnyFunction(false, "ToList", typeof(Enumerable))})},
MaZiFAU's avatar
MaZiFAU committed
            { MMTConstants.Fold,
                Aggregate},
            { MMTConstants.PropertyX,
                GetPropertyOrField("x") },
            { MMTConstants.PropertyY,
                GetPropertyOrField("y") },
            { MMTConstants.PropertyZ,
                GetPropertyOrField("z") },
            { MMTConstants.IndexList,
                IntCastedIndexer("Item") },
MaZiFAU's avatar
MaZiFAU committed
            { MMTConstants.IfThenElse,
                IfThenElse },
            { MMTConstants.GetField,
                GetPropertyOrFieldDynamic },
            { MMTConstants.Invoke,
                PartialInvoke },
        };

            private static readonly Dictionary<string, ExpressionType> MMTtoBinaryExpressionType = new()
        {
            { MMTConstants.AddRealLit,
                ExpressionType.Add},
            { MMTConstants.PointAddI,
                ExpressionType.Add},
            { "AddAssign",
                ExpressionType.AddAssign},
            { "AddAssignChecked",
                ExpressionType.AddAssignChecked},
            { "AddChecked",
                ExpressionType.AddChecked},
            { "And",
                ExpressionType.And},
            { "AndAlso",
                ExpressionType.AndAlso},
            { "AndAssign",
                ExpressionType.AndAssign},
            { "Assign",
                ExpressionType.Assign},
            { MMTConstants.Divide,
                ExpressionType.Divide},
            { "DivideAssign",
                ExpressionType.DivideAssign},
            { "Equal",
                ExpressionType.Equal},
            { "ExclusiveOr",
                ExpressionType.ExclusiveOr},
            { "ExclusiveOrAssign",
                ExpressionType.ExclusiveOrAssign},
            { "GreaterThan",
                ExpressionType.GreaterThan},
            { "GreaterThanOrEqual",
                ExpressionType.GreaterThanOrEqual},
            { "LeftShift",
                ExpressionType.LeftShift},
            { "LeftShiftAssign",
                ExpressionType.LeftShiftAssign},
            { MMTConstants.LessThan,
                ExpressionType.LessThan},
                ExpressionType.LessThanOrEqual},
            { "Modulo",
                ExpressionType.Modulo},
            { "ModuloAssign",
                ExpressionType.ModuloAssign},
            { MMTConstants.TimesRealLit,
                ExpressionType.Multiply},
            { MMTConstants.VecMultI,
                ExpressionType.Multiply},
            { MMTConstants.RealMultiplication,
                ExpressionType.Multiply},
            { "MultiplyAssign",
                ExpressionType.MultiplyAssign},
            { "MultiplyAssignChecked",
                ExpressionType.MultiplyAssignChecked},
            { "MultiplyChecked",
                ExpressionType.MultiplyChecked},
            { "NotEqual",
                ExpressionType.NotEqual},
            { "Or",
                ExpressionType.Or},
            { "OrAssign",
                ExpressionType.OrAssign},
            { "OrElse",
                ExpressionType.OrElse},
            { "Power",
                ExpressionType.Power},
            { "PowerAssign",
                ExpressionType.PowerAssign},
            { "RightShift",
                ExpressionType.RightShift},
            { "RightShiftAssign",
                ExpressionType.RightShiftAssign},
            { MMTConstants.PointSubtractI,
                ExpressionType.Subtract},
            { "SubtractAssign",
                ExpressionType.SubtractAssign},
            { "SubtractAssignChecked",
                ExpressionType.SubtractAssignChecked},
            { "SubtractChecked",
                ExpressionType.SubtractChecked},
        };

            private static readonly Dictionary<string, ExpressionType> MMTtoUnaryExpressionType = new()
        {
            //{ "Constant", // Not Unary
            //    ExpressionType.Constant},
            { "Convert",
                ExpressionType.Convert},
            { "ConvertChecked",
                ExpressionType.ConvertChecked},
            { "Decrement",
                ExpressionType.Decrement},
            { "Increment",
                ExpressionType.Increment},
            { MMTConstants.MinusRealLit,
                ExpressionType.Negate},
            { "NegateChecked",
                ExpressionType.NegateChecked},
            { "Not",
                ExpressionType.Not},
            { "OnesComplement",
                ExpressionType.OnesComplement},
            { "PostDecrementAssign",
                ExpressionType.PostDecrementAssign},
            { "PostIncrementAssign",
                ExpressionType.PostIncrementAssign},
            { "PreDecrementAssign",
                ExpressionType.PreDecrementAssign},
            { "PreIncrementAssign",
                ExpressionType.PreIncrementAssign},
            { "UnaryPlus",
                ExpressionType.UnaryPlus},
        };

            #endregion ExpressionDictionaries

            //TODO: case ((f->x)->y) instead of  assumed (f->(x->y))
            /// <summary>
            /// Maps <paramref name="URI"/> to an operation and uses the other parameters as operants to build an Expression-Tree.
            /// </summary>
            /// <param name="URI"><see cref="MMTConstants"/> to be mapped to an operation</param>
            /// <param name="lambda_applicant">arguments to be used; usually comes from <see cref="OMA.arguments"/></param>
            /// <param name="lambda_arguments">currently unused. space for a second set of parameters</param>
            /// <param name="bound_params">to be bound as parameters in <see cref="Expression.Lambda(Expression, ParameterExpression[])"/></param>
            /// <returns>encoding the current Abstract-Syntax-Tree</returns>
            /// <exception cref="ArgumentException">iff <paramref name="lambda_applicant"/> could not be used in <paramref name="URI"/></exception>
            /// <exception cref="NotImplementedException">iff <paramref name="URI"/> could not be mapped</exception>
            public static LambdaExpression MakeLambdaExpression(string URI, LambdaExpression[] lambda_applicant, LambdaExpression[] lambda_arguments, ParameterExpression[] bound_params)
            {
                void ThrowArgumentException(ExpressionType expression_cast, int expected)
                {
                    throw new ArgumentException(string.Format(
                        "\"Wrong number of Arguments. Required: {2}. Supplied: {3}.\\n\\tFor URI:\\\"{0}\\\"\\n\\tmapped to:\\\"{1}\\\"\"",
                        URI, expression_cast, expected, lambda_applicant.Count()
                    ));
                }

                ParameterExpression[] lambda_params =
                    lambda_applicant
                    .SelectMany(l => l.Parameters)
                ParameterExpression[] found_bound_params =
                    bound_params
                    .Where(p => lambda_params.Contains(p))
                    .ToArray();

                if (MMTtoUnaryExpressionType.TryGetValue(URI, out var unnary_type))
                {
                        ThrowArgumentException(unnary_type, 1);

                    Type UnarySecondArgument = lambda_applicant.Count() < 2 ? null : lambda_applicant[1].ReturnType;

                    return Expression.Lambda(Expression.MakeUnary(unnary_type, lambda_applicant[0].Body, UnarySecondArgument), found_bound_params);
                }
                else
                if (MMTtoBinaryExpressionType.TryGetValue(URI, out var binary_type))
                {
                    if (lambda_applicant.Count() != 2)
                        ThrowArgumentException(binary_type, 2);

                    return Expression.Lambda(Expression.MakeBinary(binary_type, lambda_applicant[0].Body, lambda_applicant[1].Body), found_bound_params);
                }
                else
                if (MMTtoLambdaMaker.TryGetValue(URI, out CustomFunction lamda_maker))
                {
                    return lamda_maker(lambda_applicant, lambda_arguments, found_bound_params);
                }
                else // Last entree to avoid stack-overflow:
                if (FactRecorder.AllFacts.TryGetValue(URI, out Fact fact))
                {
                    Type type = fact.CompiledValue.GetType();
                    Expression lambda_orig =
                        Expression.Constant(fact.CompiledValue, type);

                    if (FuncExtensions.IsFuncType(type, out int signature_count))
                    { // has to be LambdaExpression!
                        ParameterExpression[] invoke_params = type
                            .GetGenericArguments()
                            .Take(signature_count - 1)
                            .Select(t => Expression.Parameter(t))
                            .ToArray();

                        lambda_orig = Expression.Lambda(
                                Expression.Invoke(lambda_orig, invoke_params),
                                invoke_params
                            );
                    }

                    if (lambda_applicant.Length == 0)
                        return Expression.Lambda(lambda_orig, null);
                    else
                        return _PartialInvoke(lambda_orig, lambda_applicant, found_bound_params);
                }
                else
                if (MMTConstants.OMS_TO_TYPE.TryGetValue(URI, out Type type))
                {
                    return Expression.Lambda(Expression.Default(type), null);
                }

                throw new NotImplementedException("Could not map URI: \"" + URI + "\"");
            }

MaZiFAU's avatar
MaZiFAU committed
#pragma warning disable IDE0060 // Nicht verwendete Parameter entfernen // Signatures given by CustomFunction
            public static LambdaExpression PartialInvoke(LambdaExpression[] lambda_applicant, LambdaExpression[] lambda_arguments, ParameterExpression[] bound_params)
                => _PartialInvoke(lambda_applicant[0].Body, lambda_arguments, bound_params);

            /// <summary>
            /// May invoke a function with an incomplete parameter set.
            /// </summary>
            /// <param name="func">function to be invoked</param>
            /// <param name="lambda_arguments">arguments to invoke with</param>
            /// <param name="bound_params">to be bound as parameters in <see cref="Expression.Lambda(Expression, ParameterExpression[])"/></param>
            /// <returns>new function with up to <paramref name="lambda_arguments"/>.Count less arguments</returns>
            /// <exception cref="ArgumentException">iff <paramref name="func"/> is not a function</exception>
            public static LambdaExpression _PartialInvoke(Expression func, LambdaExpression[] lambda_arguments, ParameterExpression[] bound_params)
            {
                if (!FuncExtensions.IsFuncType(func.Type, out int signature_count))
                    throw new ArgumentException($"{nameof(func)} has to be Func");

                int params_count = signature_count - 1;
                IEnumerable<Type> params_type = func.Type.GetGenericArguments().Take(params_count);
                int free_params = params_count - lambda_arguments.Length;
                if (free_params <= 0)
                    return Expression.Lambda(
                        Expression.Invoke(
                            func,
                            lambda_arguments.Select(app => app.Body).Take(params_count)),
                        bound_params
                    );

                ParameterExpression[] new_params =
                    params_type
                    .Skip(lambda_arguments.Length)
                    .Select(t => Expression.Parameter(t))
                    .ToArray();

                return Expression.Lambda(
                    Expression.Lambda(
                        Expression.Invoke(
                            func,
                            lambda_arguments.Select(app => app.Body).AppendRange(new_params)
                        ),
                        new_params
                    ),
                    bound_params
                );
            }

            /// <summary>
            /// Chains multiple <see cref="CustomFunction"/>s into a single one. The resalt of one will be used as lambda_applicant for the next one.
            /// </summary>
            /// <param name="makes">to be chained</param>
            /// <returns>wich combines all <paramref name="makes"/>. When executed it will return the return value of the last <see cref="CustomFunction"/> in <paramref name="makes"/>.</returns>
MaZiFAU's avatar
MaZiFAU committed
            public static CustomFunction ChainMakes(CustomFunction[] makes)
                => (LambdaExpression[] lambda_applicant, LambdaExpression[] lambda_arguments, ParameterExpression[] bound_params) =>
                {
                    foreach (var make in makes)
                        lambda_applicant = new[] { make(lambda_applicant, lambda_arguments, bound_params) };

                    return lambda_applicant[0];
                };

            public static LambdaExpression ExpresionFuncToLambda(LambdaExpression func, string name_me, LambdaExpression[] args_lamda, ParameterExpression[] bound_params)
                => Expression.Lambda(Expression.Invoke(func, args_lamda.Select(l => l.Body)), name_me, bound_params);
MaZiFAU's avatar
MaZiFAU committed
            public static LambdaExpression ParseFuncUUToExpression<U>(Func<U, U> func)
                => (Expression<Func<U, U>>)((U x) => func(x));

            public static LambdaExpression MakeInvert(LambdaExpression[] lambda_applicant, LambdaExpression[] lambda_arguments, ParameterExpression[] bound_params)
                => Expression.Lambda(
                        Expression.MakeBinary(ExpressionType.Divide,
                            Expression.Convert(Expression.Constant(1), lambda_applicant[0].ReturnType),
                            lambda_applicant[0].Body),
                        bound_params
                    );

MaZiFAU's avatar
MaZiFAU committed
            public static LambdaExpression MakeSin(LambdaExpression[] lambda_applicant, LambdaExpression[] lambda_arguments, ParameterExpression[] bound_params)
                => ExpresionFuncToLambda(
                          lambda_applicant[0].ReturnType == typeof(float) ? ParseFuncUUToExpression<float>(MathF.Sin)
                        : lambda_applicant[0].ReturnType == typeof(double) ? ParseFuncUUToExpression<double>(Math.Sin)
                        : throw new NotImplementedException("Sinus for " + lambda_applicant[0].ReturnType),

                        "Sin", lambda_arguments.Length > 0 ? lambda_arguments : lambda_applicant, bound_params
MaZiFAU's avatar
MaZiFAU committed
            public static LambdaExpression MakeCos(LambdaExpression[] lambda_applicant, LambdaExpression[] lambda_arguments, ParameterExpression[] bound_params)
                => ExpresionFuncToLambda(
                          lambda_applicant[0].ReturnType == typeof(float) ? ParseFuncUUToExpression<float>(MathF.Cos)
                        : lambda_applicant[0].ReturnType == typeof(double) ? ParseFuncUUToExpression<double>(Math.Cos)
                        : throw new NotImplementedException("Cosinus for " + lambda_applicant[0].ReturnType),

                        "Cos", lambda_arguments.Length > 0 ? lambda_arguments : lambda_applicant, bound_params
MaZiFAU's avatar
MaZiFAU committed
            public static LambdaExpression MakeRoot(LambdaExpression[] lambda_applicant, LambdaExpression[] lambda_arguments, ParameterExpression[] bound_params)
                => ExpresionFuncToLambda(
                          lambda_applicant[0].ReturnType == typeof(float) ? ParseFuncUUToExpression<float>(MathF.Sqrt)
                        : lambda_applicant[0].ReturnType == typeof(double) ? ParseFuncUUToExpression<double>(Math.Sqrt)
                        : throw new NotImplementedException("Sqrt for " + lambda_applicant[0].ReturnType),

                        "Sqrt", lambda_arguments.Length > 0 ? lambda_arguments : lambda_applicant, bound_params
MaZiFAU's avatar
MaZiFAU committed
                    );

            public static LambdaExpression ProjLVecTupel(LambdaExpression[] lambda_applicant, LambdaExpression[] lambda_arguments, ParameterExpression[] bound_params)
            {
                if (lambda_applicant[0].Body is MethodCallExpression meth
                 && meth.Method.DeclaringType == typeof(Tuple)
                 && meth.Method.Name == "Create") // Access Values Directly
                {
                    return Expression.Lambda(meth.Arguments.First(), bound_params);
                }

                return (
                        lambda_applicant[0].ReturnType == typeof(Vector3)
                        ? GetPropertyOrField("x")
                        : GetPropertyOrField("Item1")
                        )
                        (lambda_applicant, lambda_arguments, bound_params);
            }
MaZiFAU's avatar
MaZiFAU committed
            public static LambdaExpression ProjRVecTupel(LambdaExpression[] lambda_applicant, LambdaExpression[] lambda_arguments, ParameterExpression[] bound_params)
                LambdaExpression[] Items_applicant;

                if (lambda_applicant[0].Body is MethodCallExpression meth
                 && meth.Method.DeclaringType == typeof(Tuple)
                 && meth.Method.Name == "Create") // Access Values Directly
                {
                    Items_applicant = meth.Arguments
                        .Skip(1)
                        .Select(arg => Expression.Lambda(arg, bound_params))
                }
                else
                {
                    Items_applicant =
                        lambda_applicant[0].ReturnType == typeof(Vector3)
                        ? new string[] { "y", "z" }
                            .Select(prop => GetPropertyOrField(prop)(lambda_applicant, lambda_arguments, bound_params))
                            .ToArray()
                        : lambda_applicant[0].ReturnType.GetProperties()
                            .OrderBy(fi => fi.Name)
                            .Skip(1) // Item1
                            .Select(fi =>
                                Expression.Lambda(
                                    Expression.Property(lambda_applicant[0].Body, fi),
                                    bound_params))
                            .ToArray();
                }
                return Items_applicant.Length == 1
                    ? Items_applicant[0]
                    : MakeTupel(Items_applicant, lambda_arguments, bound_params);
            public static LambdaExpression CalculateAngle(LambdaExpression[] lambda_applicant, LambdaExpression[] lambda_arguments, ParameterExpression[] bound_params)
                => MakeTupel(new[] {
                        ExpresionFuncToLambda(
                            (Expression<Func<Vector3, Vector3, Vector3, float>>)((Vector3 a, Vector3 b, Vector3 c)
                                => Mathf.Acos(Vector3.Dot((b - a).normalized, (b - c).normalized))),
                            "angle_between", lambda_applicant, bound_params
                        )},
                        null, bound_params
                    );

MaZiFAU's avatar
MaZiFAU committed
            public static LambdaExpression MakeTupel(LambdaExpression[] lambda_applicant, LambdaExpression[] lambda_arguments, ParameterExpression[] bound_params)
                if (lambda_applicant.Length == 3
                 && lambda_applicant.All(l => l.ReturnType == typeof(float)))

                    return ExpresionFuncToLambda(
                                (Expression<Func<float, float, float, Vector3>>)((x, y, z) => new Vector3(x, y, z)),
                                "UnityEngineVector3", lambda_applicant, bound_params
                Type[] genericTypes = new Type[lambda_applicant.Length];
                for (int i = 0; i < lambda_applicant.Length; i++)
                    genericTypes[i] = Type.MakeGenericMethodParameter(i);

                MethodInfo create = typeof(Tuple)
                    .GetMethod("Create", genericTypes)
                    .MakeGenericMethod(lambda_applicant.Select(l => l.ReturnType).ToArray());

                return Expression.Lambda(
                    Expression.Call(create, lambda_applicant.Select(l => l.Body)),
                    bound_params
                );
            }

MaZiFAU's avatar
MaZiFAU committed
            public static LambdaExpression MakeObjArray(LambdaExpression[] lambda_applicant, LambdaExpression[] lambda_arguments, ParameterExpression[] bound_params)
            {
                if (lambda_applicant.Length == 1
                 && lambda_applicant[0].ReturnType.IsArray
                 && !typeof(object[]).IsAssignableFrom(lambda_applicant[0].ReturnType))
                {
                    ParameterExpression lambda_param =
                        Expression.Parameter(lambda_applicant[0].ReturnType.GetElementType());

                    return CallAnyFunction(false, "ToArray", typeof(Enumerable))(
                        new[] {
                            CallAnyFunction(false, "Select", typeof(Enumerable),
                                new[] { (1u, Expression.Lambda(Expression.Lambda(Expression.Convert(lambda_param, typeof(object)), lambda_param))) }
                            ) (lambda_applicant, lambda_arguments, bound_params)
                        },
                        lambda_arguments,
                            Expression.NewArrayInit(
                                typeof(object),
                                lambda_applicant.Select(l => Expression.Convert(l.Body, typeof(object)))
                            ),
                            bound_params
                        );
MaZiFAU's avatar
MaZiFAU committed
            public static LambdaExpression MakeInstantList(LambdaExpression[] lambda_applicant, LambdaExpression[] lambda_arguments, ParameterExpression[] bound_params)
                => Expression.Lambda(
                        Expression.ListInit(
                            Expression.New(typeof(List<>).MakeGenericType(lambda_applicant[0].ReturnType)),
                            lambda_applicant.Select(l => l.Body) // Expression.Convert(l.Body, lambda_applicant[0].ReturnType))
MaZiFAU's avatar
MaZiFAU committed
            public static LambdaExpression InsertFrontListLiteral(LambdaExpression[] lambda_applicant, LambdaExpression[] lambda_arguments, ParameterExpression[] bound_params)
                => Expression.Lambda(
                        Expression.Call(
                            lambda_applicant[0].Body,
                            lambda_applicant[0].ReturnType.GetMethod("Insert"),
                            Expression.Constant(0, typeof(int)),
                            lambda_applicant[1].Body
                        ),
                        bound_params
                    );

MaZiFAU's avatar
MaZiFAU committed
            public static LambdaExpression MakeListEnd(LambdaExpression[] lambda_applicant, LambdaExpression[] lambda_arguments, ParameterExpression[] bound_params)
                => Expression.Lambda(
                        Expression.New(typeof(List<>).MakeGenericType(lambda_applicant[0].ReturnType))
MaZiFAU's avatar
MaZiFAU committed
            public static LambdaExpression Identity0(LambdaExpression[] lambda_applicant, LambdaExpression[] lambda_arguments, ParameterExpression[] bound_params)
                => lambda_applicant[0];

MaZiFAU's avatar
MaZiFAU committed
            public static LambdaExpression Index0(LambdaExpression[] lambda_applicant, LambdaExpression[] lambda_arguments, ParameterExpression[] bound_params)
                => Expression.Lambda(
                        Expression.ArrayIndex(lambda_applicant[0].Body, Expression.Constant(0))
                    );

MaZiFAU's avatar
MaZiFAU committed
            public static LambdaExpression Tail(LambdaExpression[] lambda_applicant, LambdaExpression[] lambda_arguments, ParameterExpression[] bound_params)
            {
                LambdaExpression enumerated =
                    CallAnyFunction(false, "Skip", typeof(Enumerable),
                        new[] { (1u, Expression.Lambda(Expression.Constant(1, typeof(int)))) }
                    )(lambda_applicant, lambda_arguments, bound_params);

                return CallAnyFunction(false, "ToArray", typeof(Enumerable))(new[] { enumerated }, null, bound_params);
            }

MaZiFAU's avatar
MaZiFAU committed
            public static LambdaExpression IfThenElse(LambdaExpression[] lambda_applicant, LambdaExpression[] lambda_arguments, ParameterExpression[] bound_params)
                => Expression.Lambda( // lambda_applicant[0] is ReturnType
                        Expression.Condition(lambda_applicant[1].Body, lambda_applicant[2].Body, lambda_applicant[3].Body),
                        bound_params
                    );

            public static LambdaExpression Aggregate(LambdaExpression[] lambda_applicant, LambdaExpression[] lambda_arguments, ParameterExpression[] bound_params)
            {
                lambda_applicant[2] = // switch order of parameters
                    Expression.Lambda(
                        Expression.Lambda(
                            ((LambdaExpression)lambda_applicant[2].Body).Body,
                            ((LambdaExpression)lambda_applicant[2].Body).Parameters.Reverse()
                        ),
                        lambda_applicant[2].Parameters
                    );

                return CallAnyFunction(false, "Aggregate", typeof(Enumerable))
                    (lambda_applicant, lambda_arguments, bound_params);
            }

            public static LambdaExpression FeedForwardUntil(LambdaExpression[] lambda_applicant, LambdaExpression[] lambda_arguments, ParameterExpression[] bound_params)
                => ChainMakes(new[]{
                       CallAnyFunction(false, "FeedForwardUntil", typeof(IEnumerableExtensions)),
                       CallAnyFunction(false, "ToList", typeof(Enumerable))}
                )(lambda_applicant[1..], lambda_arguments, bound_params);  // lambda_applicant[0] is ReturnType

            /// <summary>
            /// Find (any) generic methods and call it, given the arguments.
            /// This method tries to match the arguments to a method called <paramref name="method_name"/> in <paramref name="type"/>.
            /// If unsuccessfull, an Exception will be thrown.
            /// If the results are undecisive, the first one will be used and a log-entree created.
            /// </summary>
            /// <remarks>Although non-generic methods can be used this way, it is advised against this usage for performance reasons!</remarks>
            /// <param name="self">if <c>true</c> the method will be called on the first argument itselfe; otherwise on <paramref name="type"/></param>
            /// <param name="method_name">the name of the function to be called</param>
            /// <param name="type">the <c>Type</c> the function is a member of, or <c>null</c> iff the type of the first argument is to be used</param>
            /// <param name="lambda_manual">can be used to manually insert any number of constant arguments at any position (0-indexed)</param>
            /// <returns>that dynamically builds a method call</returns>
            /// <exception cref="Exception">Iff no matching method could be found</exception>
MaZiFAU's avatar
MaZiFAU committed
            public static CustomFunction CallAnyFunction(bool self, string method_name, Type type = null, (uint, LambdaExpression)[] lambda_manual = null)
                => (LambdaExpression[] lambda_applicant, LambdaExpression[] lambda_arguments, ParameterExpression[] bound_params) =>
                {
                    LambdaExpression[] lambda_args_new = lambda_applicant
                        .AppendRangeAt(lambda_manual)
                        .Skip(self ? 1 : 0)
                    type ??= lambda_args_new[0].ReturnType;

                    Expression[] call_args = lambda_args_new
                        .Select(l => l.Body)
                        .ToArray();

                    Type[] lambda_args_type = lambda_args_new
                        .Select(l => l.ReturnType)
                        .ToArray();

                    MethodInfo call_method =  // type.method_name
                        type.GetMethod(method_name, lambda_args_type);

                    if (call_method == null)
                        (MethodInfo m, bool Success, Dictionary<string, (Type To, int Dirty)> method_types, Expression[] partials)[] recipe =
                            type.GetMethods()
                            .Where(m =>
                                m.Name.Equals(method_name) &&
                                m.GetParameters().Length == call_args.Length
                            ).Select(m =>
                            {
                                Dictionary<string, (Type To, int Dirty)> method_types = new();

                                (bool Success, Dictionary<string, (Type To, int Dirty)> GenericParameters, Expression partials)[] test =
                                    m.GetParameters()
                                    .Zip(lambda_args_new, (par, lamb) =>
                                        IsCompatibleType(par.ParameterType, lamb.ReturnType, lamb.Body, 0))
                                    .ToArray();

                                if (!test.All(t => t.Success)
                                 || !test.All(t => method_types.TryAddAllFrom(t.GenericParameters)))
                                    return (m, false, new(), new LambdaExpression[0]);
                                    return (m, Success: true, method_types, test.Select(t => t.partials).ToArray());
                            }).Where(t => t.Success)
                            .ToArray();

                        if (recipe.Length == 0)
                            throw new Exception($"Could not find method \"{method_name}\" in Type \"{type}\" and Type Signature [{string.Join(", ", lambda_args_type.Select(o => o.ToString()))}]");
                        if (recipe.Length > 1) // AmbiguousMatchException
                            Debug.LogWarning($"Found methods not unique for \"{method_name}\" in Type \"{type}\" and Type Signature [{string.Join(", ", lambda_args_type.Select(o => o.ToString()))}]");
                        call_args = recipe[0].partials;
                        if (call_method.IsGenericMethod)
                        {
                            call_method = call_method.MakeGenericMethod(
                                call_method.GetGenericArguments()
                                .Select(arg => recipe[0].method_types[arg.Name].To)
                                .ToArray()
                            );
                        }
                    }

                    return Expression.Lambda(
                        self
                            ? Expression.Call(lambda_applicant[0].Body, call_method, call_args)
                            : Expression.Call(call_method, call_args),
                        bound_params
                    );

                    static
                    (bool Success, Dictionary<string, (Type To, int Dirty)> GenericParameters, Expression partials)
                    IsCompatibleType(Type generic, Type from_me, Expression source_from_me, int dirty)
                    {
                        Dictionary<string, (Type To, int Dirty)> retDic = new();

                        if (generic.HasElementType
                         && from_me.HasElementType)
                        {
                            generic = generic.GetElementType();
                            from_me = from_me.GetElementType();
                        }

                        if (generic.IsAssignableFrom(from_me))
                            return (true, new(), source_from_me);

                        if (generic.IsGenericType)
                        {
                            (bool Success, Dictionary<string, (Type To, int Dirty)> GenericParameters, Expression partials)[] recursion;

                            if (generic.IsInterface
                            && !from_me.IsInterface)
                            {
                                if (generic.Equals(typeof(IEnumerable<>))
                                 && from_me.IsArray) // GetInterfaces() drops here the generic type?
                                    return IsCompatibleType(
                                        generic.GetGenericArguments()[0],
                                        from_me.GetElementType(),
                                        source_from_me, //will not be Func[], atmost(?) List<Func>
                                        dirty
                                    );

                                recursion = generic.GetInterfaces().Select(gi =>
                                        from_me.GetInterfaces()
                                        .Select(fi => IsCompatibleType(gi, fi, source_from_me, dirty))
                                        .FirstOrDefault(t => t.Success)
                                    ).ToArray();
                            }
                            else
                            {
                                if (FuncExtensions.IsFuncType(generic, out int gene_sig)
                                 && FuncExtensions.IsFuncType(from_me, out int from_sig)
                                 && from_me == source_from_me.Type) // from_me <within_or_equal> source_from_me.Type
                                { // lets pretend C# uses lambda logic like MMT
                                    if (gene_sig > from_sig)
                                        return (false, new(), source_from_me);

                                    bool IsDirty = gene_sig != from_sig;
                                    if (IsDirty)
                                        dirty++;

                                    bool IsParame = source_from_me is ParameterExpression;
                                    bool IsLambda = source_from_me is LambdaExpression;
                                    if (!IsParame && !IsLambda)
                                    {
                                        Debug.LogWarning($"Unexpected Expression for Parameter \"{nameof(source_from_me)}\"");
                                        if (IsDirty)
                                            return (false, new(), source_from_me);
                                        else
                                            goto GeneralGenericCase;
                                    }

                                    Expression[] expr_decomp = null;
                                    if (IsParame)
                                    {
                                        if (IsDirty)
                                        {
                                            Debug.LogWarning(
                                                "If you see this message, you have found an example for this branch.\n" +
                                                "I have no clue what will happen now. Good Luck!");

                                            // What should it be?
                                            // Call(generic)([(from_me)=>generic](from_me))
                                            // Call([(A)=>C]=>...)([([(a,b)=>c]=>...)=>[(A)=>C]=>...]([(a,b)=>c]=>...))
                                            // [(from_me-generic)=>Call(generic)([(from_me)=>generic](from_me))]
                                            //
                                            //generic: [(A)=>C]=>...
                                            //from_me: [(a,b)=>c]=>...
                                            //result:  [(A)=>[(a,b)=>c](b)]=>...
                                            //   or?:  [(a)=>[(b)=>c]]=>...
                                            //   or?:  [[(a,b)=>c]=>C]=>...
                                            //
                                            //(from_me)=>generic]:
                                            //  [[(a,b)=>c]=>...]=>[[(A)=>C]=>...]

                                            //Lamb(Call(from_me, (A,b)), A) -> escalate b
                                        }

                                        Type[] from_args = from_me.GetGenericArguments();
                                        Type partial_type = generic
                                            .GetGenericTypeDefinition()
                                            .MakeGenericType(!IsDirty
                                                ? from_args
                                                : from_args[..(gene_sig - 1)]
                                                .Append(FuncExtensions.CreateFuncType(from_args[(gene_sig - 1)..]))
                                                .ToArray()
                                            );
                                            partial_type.GetGenericArguments()
                                            .Select(typ => Expression.Parameter(typ))
                                            .ToArray();
                                    }
                                    else
                                    if (IsLambda)
                                    {
                                        LambdaExpression source_lambda = source_from_me as LambdaExpression;

                                        ReadOnlyCollection<ParameterExpression> from_para = source_lambda.Parameters;
                                        expr_decomp =
                                            from_para.Take<Expression>(gene_sig - 1)
                                            .Append(!IsDirty
                                                ? source_lambda.Body
                                                : Expression.Lambda(source_lambda.Body, from_para.Skip(gene_sig - 1)))
                                            .ToArray();
                                    }

                                    recursion =
                                       generic.GetGenericArguments()
                                       .Zip(expr_decomp, (par, expr) => IsCompatibleType(par, expr.Type, expr, dirty))
                                       .ToArray();

                                    if (!recursion.All(t => t.Success)
                                     || !recursion.All(t => retDic.TryAddAllFrom(t.GenericParameters)))
                                        return (false, new(), source_from_me);

                                    Expression source_partial =
                                            Expression.Parameter(
                                                FuncExtensions.CreateFuncType(
                                                    recursion.Select(t => t.partials.Type).ToArray())) :
                                        IsLambda ?
                                            Expression.Lambda(
                                                recursion[^1].partials,
                                                recursion[..^1].Select(t => t.partials as ParameterExpression)
                                            ) :
                                    null; // For compiler

                                    return (true, retDic, source_partial);
                                }

                            GeneralGenericCase:

                                if (!from_me.IsGenericType
                                 || !generic.GetGenericTypeDefinition().Equals(from_me.GetGenericTypeDefinition()))
                                    return (false, new(), source_from_me);

                                recursion =
                                   generic.GetGenericArguments()
                                   .Zip(from_me.GetGenericArguments(), (par, typ) => IsCompatibleType(par, typ, source_from_me, dirty))
                                   .ToArray();
                            }

                            if (!recursion.All(t => t.Success)
                             || !recursion.All(t => retDic.TryAddAllFrom(t.GenericParameters)))
                                return (false, new(), source_from_me);
                            else
                                return (true, retDic, source_from_me);
                        }

                        if (generic.IsGenericParameter)
                        { // TODO? .GetGenericParameterConstraints()
                            return (true, new() { { generic.Name, (from_me, dirty) } }, source_from_me);
                        }

                        return (false, new(), source_from_me);
                    }
MaZiFAU's avatar
MaZiFAU committed
            public static LambdaExpression MakeType(KeyValuePair<string, LambdaExpression>[] members, ParameterExpression[] bound_params)
                => Expression.Lambda(
                        TupleFactory
                            .Create(members.Select(m => new KeyValuePair<string, Type>(m.Key, m.Value.ReturnType)))
                            .MakeNewExpression(members.Select(m => m.Value.Body)),
                        bound_params
                    );

            public static LambdaExpression GetPropertyOrFieldDynamic(LambdaExpression[] lambda_applicant, LambdaExpression[] lambda_arguments, ParameterExpression[] bound_params)
            {
                if (lambda_applicant[1].Compile() is not Func<string> name_gen)
                    throw new ArgumentException($"Second element of {nameof(lambda_applicant)} hast to resolve to {typeof(string)}, argumentless!");

                return GetPropertyOrField(name_gen())
                    (lambda_applicant.Where((l, i) => i != 1).ToArray(), lambda_arguments, bound_params);
            }

            public static CustomFunction GetPropertyOrField(string property_name)
                => (LambdaExpression[] lambda_applicant, LambdaExpression[] lambda_arguments, ParameterExpression[] bound_params)
                => lambda_applicant.Length == 1
                    ? Expression.Lambda(
                        Expression.PropertyOrField(lambda_applicant[0].Body, property_name),
                        bound_params
MaZiFAU's avatar
MaZiFAU committed
                        Expression.Property(lambda_applicant[0].Body, property_name,
                            lambda_applicant.Skip(1).Select(l => l.Body).ToArray()),
            /// <summary>
            /// Casts lambda_arguments into <c>int</c>s and uses them to (multidimensional) index lambda_applicant.<paramref name="property_name"/>
            /// </summary>
            /// <param name="property_name">name of property to index</param>
MaZiFAU's avatar
MaZiFAU committed
            public static CustomFunction IntCastedIndexer(string property_name)
                => (LambdaExpression[] lambda_applicant, LambdaExpression[] lambda_arguments, ParameterExpression[] bound_params)
                => Expression.Lambda(
MaZiFAU's avatar
MaZiFAU committed
                        Expression.Property(lambda_applicant[0].Body, property_name,
                            lambda_applicant.Skip(1).Select(l => Expression.Convert(l.Body, typeof(int))).ToArray()),
MaZiFAU's avatar
MaZiFAU committed
#pragma warning restore IDE0060 // Nicht verwendete Parameter entfernen // Signatures given by CustomFunction