using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Linq; using System.Linq.Expressions; using System.Reflection; using UnityEditor; 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) { 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(); compile_base = lambda_orig.Body; } else { signature_count = lambda_orig.Parameters.Count + 1; signature_list = Enumerable.Append( lambda_orig.Parameters.Select(p => p.Type), lambda_orig.ReturnType) .ToList(); compile_base = lambda_orig; } 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) : Expression.NewArrayInit( typeof(object), new Expression[] { Expression.Convert(Expression.Invoke(compile_base, cast_new_to_signature), typeof(object)) }), object_arr ); return final_expression.Compile() as Func<object[], object[]>; } /// <summary> /// Libary and configuartions to translate a SOMDoc AST into an Expression-Tree /// </summary> 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> public delegate LambdaExpression CustomFunction(LambdaExpression[] lambda_applicant, LambdaExpression[] lambda_arguments, ParameterExpression[] bound_params); private static readonly Dictionary<string, CustomFunction> MMTtoLambdaMaker = new() { { MMTConstants.InvertRealLit, MakeInvert }, { MMTConstants.Sin, MakeSin }, { MMTConstants.Cos, MakeCos }, { MMTConstants.SquareRoot, MakeRoot }, { MMTConstants.Tuple, MakeTupel }, { MMTConstants.Product, // TODO:Automate MakeTupel }, { MMTConstants.MakeObjectArray, MakeObjArray }, { MMTConstants.ListApplicant, 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, ProjLVecTupel }, { MMTConstants.ProjR, ProjRVecTupel }, { MMTConstants.Map, ChainMakes(new[]{ CallAnyFunction(false, "Select", typeof(Enumerable)), CallAnyFunction(false, "ToList", typeof(Enumerable))})}, { MMTConstants.FeedForwardWhile, FeedForwardUntil }, { MMTConstants.FeedForwardWhileT2, FeedForwardUntil }, { 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))})}, { MMTConstants.Filter2, ChainMakes(new[]{ CallAnyFunction(false, "Where", typeof(Enumerable)), CallAnyFunction(false, "ToList", typeof(Enumerable))})}, { MMTConstants.FilterT2, ChainMakes(new[]{ CallAnyFunction(false, "Where", typeof(Enumerable)), CallAnyFunction(false, "ToList", typeof(Enumerable))})}, { MMTConstants.Fold, Aggregate}, { MMTConstants.PropertyX, GetPropertyOrField("x") }, { MMTConstants.PropertyY, GetPropertyOrField("y") }, { MMTConstants.PropertyZ, GetPropertyOrField("z") }, { MMTConstants.IndexList, IntCastedIndexer("Item") }, { 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}, { MMTConstants.LEQRealLit, ExpressionType.LessThanOrEqual}, { "Modulo", ExpressionType.Modulo}, { "ModuloAssign", ExpressionType.ModuloAssign}, { MMTConstants.TimesRealLit, ExpressionType.Multiply}, { MMTConstants.VecMultI, 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) .ToArray(); ParameterExpression[] found_bound_params = bound_params .Where(p => lambda_params.Contains(p)) .ToArray(); if (MMTtoUnaryExpressionType.TryGetValue(URI, out var unnary_type)) { if (lambda_applicant.Count() < 1) 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 if (MMTConstants.OMS_TO_TYPE.TryGetValue(URI, out Type type)) { return Expression.Lambda(Expression.Default(type), null); } else // Last entree to avoid stack-overflow: if (FactRecorder.AllFacts.TryGetValue(URI, out Fact fact)) { 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); } throw new NotImplementedException("Could not map URI: \"" + URI + "\""); } #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> 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); 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 ); 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 ); 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 ); 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 ); 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); } 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)) .ToArray(); } 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 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 ); } 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, bound_params ); } return Expression.Lambda( Expression.NewArrayInit( typeof(object), lambda_applicant.Select(l => Expression.Convert(l.Body, typeof(object))) ), bound_params ); } 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)) ), bound_params ); 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 ); public static LambdaExpression MakeListEnd(LambdaExpression[] lambda_applicant, LambdaExpression[] lambda_arguments, ParameterExpression[] bound_params) => Expression.Lambda( Expression.New(typeof(List<>).MakeGenericType(lambda_applicant[0].ReturnType)) ); public static LambdaExpression Identity0(LambdaExpression[] lambda_applicant, LambdaExpression[] lambda_arguments, ParameterExpression[] bound_params) => lambda_applicant[0]; public static LambdaExpression Index0(LambdaExpression[] lambda_applicant, LambdaExpression[] lambda_arguments, ParameterExpression[] bound_params) => Expression.Lambda( Expression.ArrayIndex(lambda_applicant[0].Body, Expression.Constant(0)) ); 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); } 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> 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) .ToArray(); 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) { // call_method is overloaded or generic (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]); else 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_method = recipe[0].m; 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() ); expr_decomp = 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 = IsParame ? 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); } }; 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 ) : Expression.Lambda( Expression.Property(lambda_applicant[0].Body, property_name, lambda_applicant.Skip(1).Select(l => l.Body).ToArray()), bound_params ); /// <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> public static CustomFunction IntCastedIndexer(string property_name) => (LambdaExpression[] lambda_applicant, LambdaExpression[] lambda_arguments, ParameterExpression[] bound_params) => Expression.Lambda( Expression.Property(lambda_applicant[0].Body, property_name, lambda_applicant.Skip(1).Select(l => Expression.Convert(l.Body, typeof(int))).ToArray()), bound_params ); #pragma warning restore IDE0060 // Nicht verwendete Parameter entfernen // Signatures given by CustomFunction } } }