Skip to content
Snippets Groups Projects
SOMDocToLambdaExpression.cs 18.7 KiB
Newer Older
  • Learn to ignore specific revisions
  • using System.Collections.Generic;
    using System.Linq.Expressions;
    using System;
    using System.Linq;
    using UnityEngine;
    
    using System.Reflection;
    using MoreLinq;
    
    
    namespace REST_JSON_API
    {
        abstract public partial class SOMDoc
        {
            protected static class SOMDocToLambdaExpression<T>
            {
                // TODO: Populate Dictionaries
                #region ExpressionDictionaries
    
                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,
                    MakeCos },
                { MMTConstants.Tuple,
                    MakeTupel },
                { MMTConstants.MakeObjectArray,
                    MakeObjArray },
                { "InstantList",
                    MakeInstantList },
                { MMTConstants.ListEnd,
                    MakeListEnd },
                { MMTConstants.ListLiteral,
                    InsertFrontListLiteral },
                { MMTConstants.ListType,
                    Identity0 },
    
                { MMTConstants.ProjL,
                    GetPropertyOrField("Item1") },
                { MMTConstants.ProjR,
                    ProjRTupel },
                { MMTConstants.FeedForwardWhile,
                    CallAnyFunction(false, "FeedForwardWhile", typeof(IEnumerableExtensions)) },
                { MMTConstants.PartialAggregate,
                    CallAnyFunction(false, "PartialAggregate", typeof(IEnumerableExtensions)) },
                { MMTConstants.ToArray,
                    CallAnyFunction(false, "ToArray", typeof(Enumerable)) },
                { MMTConstants.PropertyX,
                    GetPropertyOrField("x") },
                { MMTConstants.PropertyY,
                    GetPropertyOrField("y") },
                { MMTConstants.PropertyZ,
                    GetPropertyOrField("z") },
                { MMTConstants.TupelToVector,
                    TupelToVector },
    
            };
    
                private static readonly Dictionary<string, ExpressionType> MMTtoBinaryExpressionType = new()
            {
    
                { MMTConstants.AddRealLit,
    
                    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},
                { "LessThanOrEqual",
                    ExpressionType.LessThanOrEqual},
                { "Modulo",
                    ExpressionType.Modulo},
                { "ModuloAssign",
                    ExpressionType.ModuloAssign},
    
                { MMTConstants.TimesRealLit,
    
                    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.Subtract,
                    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},
                { "Negate",
                    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))
                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)
                        .ToArray(); //PERF: .ToList().Sort() => .BinarySearch; //Too much overhead?
                    ParameterExpression[] found_bound_params =
                        bound_params
                        .Where(p => lambda_params.Contains(p))
                        .ToArray();
    
                    if (MMTtoUnaryExpressionType.TryGetValue(URI, out var unnary_type))
                    {
                        if (found_bound_params.Count() < 1)
                            ThrowArgumentException(unnary_type, 1);
    
                        Type UnarySecondArgument = found_bound_params.Count() < 2 ? null : found_bound_params[1].Type;
    
                        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
                    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 + "\"");
                }
    
                private static LambdaExpression ExpresionFuncToLambda(LambdaExpression func, string name, LambdaExpression[] args_lamda, ParameterExpression[] bound_params, uint nTargs_fallback)
                    => Expression.Lambda(Expression.Invoke(func, args_lamda.Select(l => l.Body)), name, bound_params);
    
                private static LambdaExpression ParseFuncUUToExpression<U>(Func<U, U> func)
                    => (Expression<Func<U, U>>)((U x) => func(x));
    
                private 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, 1
                        );
    
                private 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, 1
                        );
    
    
                private static LambdaExpression ProjRTupel(LambdaExpression[] lambda_applicant, LambdaExpression[] lambda_arguments, ParameterExpression[] bound_params)
                {
                    LambdaExpression[] Items_applicant = 
                        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 MakeTupel(Items_applicant, lambda_arguments, bound_params);
                }
    
    
                private static LambdaExpression MakeTupel(LambdaExpression[] lambda_applicant, LambdaExpression[] lambda_arguments, ParameterExpression[] 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
                    );
                }
    
                private static LambdaExpression TupelToVector(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, 3
                                );
                    else
    
                        return ExpresionFuncToLambda(
                                    (Expression<Func<Tuple<float, float, float>, Vector3>>)
                                        (tupel => new Vector3(tupel.Item1, tupel.Item2, tupel.Item3)),
                                    "TupelToUnityEngineVector3", lambda_applicant, bound_params, 1
                                );
    
                }
    
                private static LambdaExpression MakeObjArray(LambdaExpression[] lambda_applicant, LambdaExpression[] lambda_arguments, ParameterExpression[] bound_params)
                    => Expression.Lambda(
                            Expression.NewArrayInit(
                                typeof(object),
                                lambda_applicant.Select(l => Expression.Convert(l.Body, typeof(object)))
                            ),
                            bound_params
                        );
    
                private 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_arguments.Select(l => l.Body) // Expression.Convert(l.Body, lambda_applicant[0].ReturnType))
                            ),
                            bound_params
                        );
    
                private static LambdaExpression InsertFrontListLiteral(LambdaExpression[] lambda_applicant, LambdaExpression[] lambda_arguments, ParameterExpression[] bound_params)
                    => Expression.Lambda(
                            Expression.Call(
                                lambda_applicant[0].ReturnType.GetMethod("Insert"),
    
                                lambda_applicant[0].Body,
    
                                Expression.Constant(0, typeof(int)),
                                lambda_applicant[1].Body
                            ),
                            bound_params
                        );
    
                private static LambdaExpression MakeListEnd(LambdaExpression[] lambda_applicant, LambdaExpression[] lambda_arguments, ParameterExpression[] bound_params)
                    => Expression.Lambda(
    
                            Expression.New(typeof(List<>).MakeGenericType(lambda_applicant[0].ReturnType))
    
                        );
    
                private static LambdaExpression Identity0(LambdaExpression[] lambda_applicant, LambdaExpression[] lambda_arguments, ParameterExpression[] bound_params)
                    => lambda_applicant[0];
    
    
                private static LambdaExpression Index0(LambdaExpression[] lambda_applicant, LambdaExpression[] lambda_arguments, ParameterExpression[] bound_params)
                    => Expression.Lambda(
                            Expression.ArrayIndex(lambda_applicant[0].Body, Expression.Constant(0))
                        );
    
                private 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);
                }
    
                private 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) =>
                    {
                        type ??= lambda_applicant[0].ReturnType;
    
                        LambdaExpression[] lambda_args_new = lambda_applicant
                            .Skip(self ? 1 : 0)
                            .AppendRangeAt(lambda_manual)
                            .ToArray();
    
                        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)
                        {
                            call_method = type.GetMethod(method_name);
    
                            if (call_method == null)
                                throw new Exception($"Could not find method \"{method_name}\" in Type \"{type}\" and Type Signature [{lambda_args_type}]");
    
                            if (call_method.IsGenericMethod)
                            {
                                Type[] distinct_types = lambda_args_type
                                    .SelectMany(l =>
                                        !l.IsGenericType
                                        ? new Type[] { l }
                                        : l.GetGenericArguments())
                                    .Distinct()
                                    .ToArray();
    
                                int n_generics = call_method.GetGenericArguments().Length;
                                call_method = call_method.MakeGenericMethod(distinct_types[0..n_generics]);
                            }
                        }
    
                        return Expression.Lambda(
                            self
                                ? Expression.Call(lambda_applicant[0].Body, call_method, call_args)
                                : Expression.Call(call_method, call_args),
                            bound_params
                        );
                    };
    
                private static CustomFunction GetPropertyOrField(string property_name)
                    => (LambdaExpression[] lambda_applicant, LambdaExpression[] lambda_arguments, ParameterExpression[] bound_params)
                    => Expression.Lambda(
                            Expression.PropertyOrField(lambda_applicant[0].Body, property_name),
                            bound_params
                        );