using System.Collections.Generic; using System.Linq.Expressions; using System; using System.Linq; using UnityEngine; using System.Reflection; using UnityEditor; using System.Collections.ObjectModel; using UnityEngine.Assertions; 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.Product, // TODO:Automate MakeTupel }, { MMTConstants.MakeObjectArray, MakeObjArray }, { MMTConstants.ListApplicant, MakeInstantList }, { MMTConstants.ListEnd, MakeListEnd }, { MMTConstants.ListLiteral, InsertFrontListLiteral }, { MMTConstants.ListType, Identity0 }, { MMTConstants.ProjL, ProjLVecTupel }, { MMTConstants.ProjR, ProjRVecTupel }, { MMTConstants.Map, ChainMakes(new[]{ CallAnyFunction(false, "Select", typeof(Enumerable)), CallAnyFunction(false, "ToArray", typeof(Enumerable))})}, { 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") }, }; 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 (FactRecorder.AllFacts.TryGetValue(URI, out Fact fact)) { MMTFact decl = fact.MakeMMTDeclaration(); SOMDoc df = decl is MMTGeneralFact gf ? gf.defines : (decl as MMTValueFact).lhs; return df.GetLambdaExpression(); } else 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 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]; }; 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 ProjLVecTupel(LambdaExpression[] lambda_applicant, LambdaExpression[] lambda_arguments, ParameterExpression[] bound_params) => ( lambda_applicant[0].ReturnType == typeof(Vector3) ? GetPropertyOrField("x") : GetPropertyOrField("Item1") ) (lambda_applicant, lambda_arguments, bound_params); private static LambdaExpression ProjRVecTupel(LambdaExpression[] lambda_applicant, LambdaExpression[] lambda_arguments, ParameterExpression[] bound_params) { LambdaExpression[] 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); } private 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, 3 ); 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 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 ); } 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_applicant.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].Body, lambda_applicant[0].ReturnType.GetMethod("Insert"), 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 is overloaded or generic (MethodInfo m, bool Success, Dictionary<string, (Type To, int Dirty)> method_types)[] 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()); else return (m, Success: true, method_types); }).Where(t => t.Success) .ToArray(); if (recipe.Length == 0) throw new Exception($"Could not find method \"{method_name}\" in Type \"{type}\" and Type Signature [{lambda_args_type}]"); if (recipe.Length > 1) // AmbiguousMatchException Debug.LogWarning($"Found methods not unique for \"{method_name}\" in Type \"{type}\" and Type Signature [{lambda_args_type}]"); call_method = recipe[0].m; if (call_method.IsGenericMethod) { call_method = call_method.MakeGenericMethod( call_method.GetGenericArguments() .Select(arg => recipe[0].method_types[arg.Name].To) .ToArray() ); } ParameterInfo[] call_para = call_method.GetParameters(); for (int i = 0; i < lambda_args_new.Length; i++) { if (!call_para[i].ParameterType .GetGenericArguments() .Any(arg => recipe[0].method_types[arg.Name].Dirty) ) continue; } ; } 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(); (bool Success, Dictionary<string, (Type To, int Dirty)> GenericParameters, Expression partials) retVal = (false, retDic, source_from_me); 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) { Type[] from_args = from_me.GetGenericArguments(); Type partial_type = generic .GetGenericTypeDefinition() .MakeGenericType( from_args[..(gene_sig - 1)] .Append(FuncExtensions.CreateFuncType(from_args[(gene_sig - 1)..])) .ToArray() ); //[(a)=>c] //[(a,b)=>c] // //[(a)=>[(a,b)=>c](b)] expr_decomp = // schould it be (from_me)=>generic 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); } }; 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 ); } } }