diff --git a/csharp/Lib/Utils/Operators.cs b/csharp/Lib/Utils/Operators.cs new file mode 100644 index 000000000..644cb2906 --- /dev/null +++ b/csharp/Lib/Utils/Operators.cs @@ -0,0 +1,95 @@ +using System.Reflection; +using static System.Reflection.BindingFlags; + +namespace InnovEnergy.Lib.Utils; + +public static class Operators +{ + public static Func CreateBinaryOpForProps(this String op) + { + var methodName = GetMethodNameForBinaryOp(op); + + var props = typeof(T) + .GetProperties(Instance | Public) + .Where(p => p.CanWrite) + .Select(p => + ( + prop: p, + op: GetOpMethod(methodName, p.PropertyType) ?? + throw new ArgumentException($"Type {p.PropertyType.Name} " + + $"of property {p.Name} " + + $"has no suitable {op} operator defined.")) + ) + .ToArray(); + + var ctr = typeof(T).GetConstructors().FirstOrDefault(c => c.GetParameters().Length == 0); + + if (ctr is null) + throw new ArgumentException($"Type {typeof(T).Name} has no suitable parameterless constructor."); + + + T Op(T left, T right) + { + // TODO: make this faster/nicer using Expression API (low prio) + + var result = ctr.Invoke(null); + + foreach (var (p, m) in props) + { + var l = p.GetValue(left); + var r = p.GetValue(right); + + var s = m.Invoke(null, new[] { l, r }); + + p.SetValue(result, s); + } + + return (T) result; + } + + return Op; + } + + private static String GetMethodNameForBinaryOp(String op) + { + // from https://stackoverflow.com/a/29495075 + + return op switch + { + "&" => "op_BitwiseAnd", + "|" => "op_BitwiseOr", + "+" => "op_Addition", + "-" => "op_Subtraction", + "/" => "op_Division", + "%" => "op_Modulus", + "*" => "op_Multiply", + "<<" => "op_LeftShift", + ">>" => "op_RightShift", + "^" => "op_ExclusiveOr", + "==" => "op_Equality", + "!=" => "op_Inequality", + ">" => "op_GreaterThan", + "<" => "op_LessThan", + ">=" => "op_GreaterThanOrEqual", + "<=" => "op_LessThanOrEqual", + _ => throw new ArgumentException("unknown operator", nameof(op)) + }; + } + + private static MethodInfo? GetOpMethod(String? methodName, Type type) + { + return type + .GetMethods(Static | Public) + .FirstOrDefault(m => m.Name == methodName + && m.ReturnType == type + && m.IsMonoidOp()); + } + + private static Boolean IsMonoidOp(this MethodInfo m) + { + var ps = m.GetParameters(); + + return ps.Length == 2 && + ps.All(p => p.ParameterType == m.ReturnType); + } +} \ No newline at end of file