diff options
| author | jp <jp_bruyere@hotmail.com> | 2016-03-18 12:39:38 +0100 |
|---|---|---|
| committer | jp <jp_bruyere@hotmail.com> | 2016-03-18 12:39:38 +0100 |
| commit | 22644afe6f0722dcfb5fd27ba353f12274f55eb6 (patch) | |
| tree | 7472299f2c96d9a071b6101bd97542b8e8fce306 /src/CompilerServices | |
| parent | e5069093ba483ef8644c5cac685d76f2600606f0 (diff) | |
| parent | 356bfaaed4f46a727ae033f37f362b062c8c32d9 (diff) | |
Merge branch 'master' of https://github.com/jpbruyere/Crow
Diffstat (limited to 'src/CompilerServices')
| -rw-r--r-- | src/CompilerServices/CompilerServices.cs | 389 |
1 files changed, 382 insertions, 7 deletions
diff --git a/src/CompilerServices/CompilerServices.cs b/src/CompilerServices/CompilerServices.cs index da5c0fe4..cd491c4d 100644 --- a/src/CompilerServices/CompilerServices.cs +++ b/src/CompilerServices/CompilerServices.cs @@ -50,6 +50,8 @@ namespace Crow string dynMethodId = ""; bool resolved = false; + public bool TwoWayBinding = false; + public string NewDynMethodId { get { if (!string.IsNullOrEmpty (dynMethodId)) @@ -88,19 +90,32 @@ namespace Crow #region CTOR public Binding(){} - public Binding(MemberReference _source, string _expression) + public Binding(MemberReference _target, string _expression) { - Target = _source; + Target = _target; Expression = _expression; } - public Binding(object _source, string _member, string _expression) + public Binding(object _target, string _member, string _expression) { - Target = new MemberReference (_source, _source.GetType().GetMember (_member) [0]); + Target = new MemberReference (_target, _target.GetType().GetMember (_member) [0]); Expression = _expression; } + public Binding(object _target, string _targetMember, object _source, string _sourceMember) + { + Target = new MemberReference (_target, _target.GetType().GetMember (_targetMember) [0]); + Source = new MemberReference (_source, _source.GetType().GetMember (_sourceMember) [0]); + } + public Binding(MemberReference _target, MemberReference _source) + { + Target = _target; + Source = _source; + } #endregion - public bool FindTarget(){ + public bool FindSource(){ + if (Source != null) + return true; + string member = null; //if binding exp = '{}' => binding is done on datasource @@ -112,7 +127,14 @@ namespace Crow return true; } - string[] bindingExp = Expression.Split ('/'); + string expression = Expression; + + if (Expression.StartsWith ("²")) { + expression = expression.Substring (1); + TwoWayBinding = true; + } + + string[] bindingExp = expression.Split ('/'); if (bindingExp.Length == 1) { //datasource binding @@ -164,10 +186,18 @@ namespace Crow return false; } - if (Source.FindMember (member)) + if (Source.FindMember (member)) { + if (TwoWayBinding) { + IBindable source = Source.Instance as IBindable; + if (source == null) + throw new Exception (Target.Instance.ToString() + " does not implement IBindable for 2 way bindings"); + source.Bindings.Add (new Binding (Source, Target)); + } return true; + } Debug.WriteLine ("Binding member not found: " + member); + Source = null; return false; } public void Reset() @@ -186,6 +216,351 @@ namespace Crow public static class CompilerServices { + public static void ResolveBindings(List<Binding> Bindings) + { + if (Bindings == null) + return; + if (Bindings.Count == 0) + return; + #if DEBUG_BINDING + Debug.WriteLine ("Resolve Bindings => " + this.ToString ()); + #endif + //grouped bindings by Instance of Source + Dictionary<object,List<Binding>> resolved = new Dictionary<object, List<Binding>>(); + + foreach (Binding b in Bindings) { + if (b.Resolved) + continue; + if (b.Target.Member.MemberType == MemberTypes.Event) { + if (b.Expression.StartsWith("{")){ + CompilerServices.CompileEventSource(b); + continue; + } + if (!b.FindSource ()) + continue; + //register handler for event + if (b.Source.Method == null) { + Debug.WriteLine ("\tError: Handler Method not found: " + b.ToString()); + continue; + } + try { + MethodInfo addHandler = b.Target.Event.GetAddMethod (); + Delegate del = Delegate.CreateDelegate (b.Target.Event.EventHandlerType, b.Source.Instance, b.Source.Method); + addHandler.Invoke (b.Target.Instance, new object[] { del }); + + #if DEBUG_BINDING + Debug.WriteLine ("\tHandler binded => " + b.ToString()); + #endif + b.Resolved = true; + } catch (Exception ex) { + Debug.WriteLine ("\tERROR: " + ex.ToString()); + } + continue; + } + + if (!b.FindSource ()) + continue; + + List<Binding> bindings = null; + if (!resolved.TryGetValue (b.Source.Instance, out bindings)) { + bindings = new List<Binding> (); + resolved [b.Source.Instance] = bindings; + } + bindings.Add (b); + b.Resolved = true; + } + + MethodInfo stringEquals = typeof(string).GetMethod + ("Equals", new Type[3] {typeof(string), typeof(string), typeof(StringComparison)}); + Type target_Type = Bindings[0].Target.Instance.GetType(); + EventInfo ei = typeof(IValueChange).GetEvent("ValueChanged"); + MethodInfo evtInvoke = ei.EventHandlerType.GetMethod ("Invoke"); + ParameterInfo[] evtParams = evtInvoke.GetParameters (); + Type handlerArgsType = evtParams [1].ParameterType; + Type[] args = {typeof(object), typeof(object),handlerArgsType}; + FieldInfo fiNewValue = typeof(ValueChangeEventArgs).GetField("NewValue"); + FieldInfo fiMbName = typeof(ValueChangeEventArgs).GetField("MemberName"); + + //group;only one dynMethods by target (valuechanged event source) + //changed value name tested in switch + //IEnumerable<Binding[]> groupedByTarget = resolved.GroupBy (g => g.Target.Instance, g => g, (k, g) => g.ToArray ()); + foreach (List<Binding> grouped in resolved.Values) { + int i = 0; + Type source_Type = grouped[0].Source.Instance.GetType(); + + DynamicMethod dm = null; + ILGenerator il = null; + + System.Reflection.Emit.Label[] jumpTable = null; + System.Reflection.Emit.Label endMethod = new System.Reflection.Emit.Label(); + + #region Retrieve EventHandler parameter type + //EventInfo ei = targetType.GetEvent ("ValueChanged"); + //no dynamic update if ValueChanged interface is not implemented + if (source_Type.GetInterfaces().Contains(typeof(IValueChange))){ + dm = new DynamicMethod(grouped[0].NewDynMethodId, + MethodAttributes.Family | MethodAttributes.FamANDAssem | MethodAttributes.NewSlot, + CallingConventions.Standard, + typeof(void), + args, + target_Type,true); + + il = dm.GetILGenerator(256); + + endMethod = il.DefineLabel(); + jumpTable = new System.Reflection.Emit.Label[grouped.Count]; + for (i = 0; i < grouped.Count; i++) + jumpTable [i] = il.DefineLabel (); + il.DeclareLocal(typeof(string)); + il.DeclareLocal(typeof(object)); + + il.Emit(OpCodes.Nop); + il.Emit(OpCodes.Ldarg_0); + //il.Emit(OpCodes.Isinst, sourceType); + //push new value onto stack + il.Emit(OpCodes.Ldarg_2); + il.Emit(OpCodes.Ldfld, fiNewValue); + il.Emit(OpCodes.Stloc_1); + //push name + il.Emit(OpCodes.Ldarg_2); + il.Emit(OpCodes.Ldfld, fiMbName); + il.Emit(OpCodes.Stloc_0); + il.Emit(OpCodes.Ldloc_0); + il.Emit(OpCodes.Brfalse, endMethod); + } + #endregion + + i = 0; + foreach (Binding b in grouped) { + #region initialize target with actual value + object targetValue = null; + if (b.Source.Member != null){ + if (b.Source.Member.MemberType == MemberTypes.Property) + targetValue = b.Source.Property.GetGetMethod ().Invoke (b.Source.Instance, null); + else if (b.Source.Member.MemberType == MemberTypes.Field) + targetValue = b.Source.Field.GetValue (b.Source.Instance); + else if (b.Source.Member.MemberType == MemberTypes.Method){ + MethodInfo mthSrc = b.Source.Method; + if (mthSrc.IsDefined(typeof(ExtensionAttribute), false)) + targetValue = mthSrc.Invoke(null, new object[] {b.Source.Instance}); + else + targetValue = mthSrc.Invoke(b.Source.Instance, null); + }else + throw new Exception ("unandled source member type for binding"); + }else if (string.IsNullOrEmpty(b.Expression)) + targetValue= grouped [0].Source.Instance;//empty binding exp=> bound to target object by default + //TODO: handle other dest type conversions + if (b.Target.Property.PropertyType == typeof(string)){ + if (targetValue == null){ + //set default value + + }else + targetValue = targetValue.ToString (); + } + if (targetValue != null) + b.Target.Property.GetSetMethod ().Invoke + (b.Target.Instance, new object[] { b.Target.Property.PropertyType.Cast(targetValue)}); + else + b.Target.Property.GetSetMethod ().Invoke + (b.Target.Instance, new object[] { targetValue }); + #endregion + + //if no dyn update, skip jump table + if (il == null) + continue; + + il.Emit (OpCodes.Ldloc_0); + if (b.Source.Member != null) + il.Emit (OpCodes.Ldstr, b.Source.Member.Name); + else + il.Emit (OpCodes.Ldstr, b.Expression.Split('/').LastOrDefault()); + il.Emit (OpCodes.Ldc_I4_4);//StringComparison.Ordinal + il.Emit (OpCodes.Callvirt, stringEquals); + il.Emit (OpCodes.Brtrue, jumpTable[i]); + i++; + } + + if (il == null) + continue; + + il.Emit (OpCodes.Br, endMethod); + + i = 0; + foreach (Binding b in grouped) { + + il.MarkLabel (jumpTable [i]); + il.Emit(OpCodes.Ldloc_1); + + //by default, target value type is deducted from source member type to allow + //memberless binding, if targetMember exists, it will be used to determine target + //value type for conversion + Type targetValueType = b.Target.Property.PropertyType; + if (b.Source.Member != null) { + if (b.Source.Member.MemberType == MemberTypes.Property) + targetValueType = b.Source.Property.PropertyType; + else if (b.Source.Member.MemberType == MemberTypes.Field) + targetValueType = b.Source.Field.FieldType; + else + throw new Exception ("unhandle target member type in binding"); + } + + if (b.Target.Property.PropertyType == typeof(string)) { + MemberReference tostring = new MemberReference (b.Target.Instance); + if (!tostring.FindMember ("ToString")) + throw new Exception ("ToString method not found"); + il.Emit (OpCodes.Callvirt, tostring.Method); + }else if (!targetValueType.IsValueType) + il.Emit(OpCodes.Castclass, targetValueType); + else if (b.Target.Property.PropertyType != targetValueType) + il.Emit(OpCodes.Callvirt, CompilerServices.GetConvertMethod( b.Target.Property.PropertyType )); + else + il.Emit(OpCodes.Unbox_Any, b.Target.Property.PropertyType); + + il.Emit(OpCodes.Callvirt, b.Target.Property.GetSetMethod()); + il.Emit (OpCodes.Br, endMethod); + i++; + + } + il.MarkLabel(endMethod); + il.Emit(OpCodes.Pop); + il.Emit(OpCodes.Ret); + + Delegate del = dm.CreateDelegate(ei.EventHandlerType, Bindings[0].Target.Instance); + MethodInfo addHandler = ei.GetAddMethod (); + addHandler.Invoke(grouped [0].Source.Instance, new object[] {del}); + } + } + + /// <summary> + /// Compile events expression in GOML attributes + /// </summary> + /// <param name="es">Event binding details</param> + public static void CompileEventSource(Binding binding) + { + #if DEBUG_BINDING + Debug.WriteLine ("\tCompile Event Source => " + binding.ToString()); + #endif + + Type target_type = binding.Target.Instance.GetType(); + + #region Retrieve EventHandler parameter type + MethodInfo evtInvoke = binding.Target.Event.EventHandlerType.GetMethod ("Invoke"); + ParameterInfo[] evtParams = evtInvoke.GetParameters (); + Type handlerArgsType = evtParams [1].ParameterType; + #endregion + + Type[] args = {typeof(object), typeof(object),handlerArgsType}; + DynamicMethod dm = new DynamicMethod(binding.NewDynMethodId, + typeof(void), + args, + target_type); + + + #region IL generation + ILGenerator il = dm.GetILGenerator(256); + + string src = binding.Expression.Trim(); + + if (! (src.StartsWith("{") || src.EndsWith ("}"))) + throw new Exception (string.Format("GOML:Malformed {0} Event handler: {1}", binding.Target.Member.Name, binding.Expression)); + + src = src.Substring (1, src.Length - 2); + string[] srcLines = src.Split (new char[] { ';' }); + + foreach (string srcLine in srcLines) { + string statement = srcLine.Trim (); + + string[] operandes = statement.Split (new char[] { '=' }); + if (operandes.Length < 2) //not an affectation + { + continue; + } + string lop = operandes [0].Trim (); + string rop = operandes [operandes.Length-1].Trim (); + + #region LEFT OPERANDES + GraphicObject lopObj = binding.Target.Instance as GraphicObject; //default left operand base object is + //the first arg (object sender) of the event handler + + il.Emit(OpCodes.Ldarg_0); //load sender ref onto the stack + + string[] lopParts = lop.Split (new char[] { '.' }); + if (lopParts.Length > 1) {//should search also for member of es.Source + MethodInfo FindByNameMi = typeof(GraphicObject).GetMethod("FindByName"); + for (int j = 0; j < lopParts.Length - 1; j++) { + il.Emit (OpCodes.Ldstr, lopParts[j]); + il.Emit(OpCodes.Callvirt, FindByNameMi); + } + } + + int i = lopParts.Length -1; + + MemberInfo[] lopMbis = lopObj.GetType().GetMember (lopParts[i]); + + if (lopMbis.Length<1) + throw new Exception (string.Format("CROW BINDING: Member not found '{0}'", lop)); + + OpCode lopSetOC; + dynamic lopSetMbi; + Type lopT = null; + switch (lopMbis[0].MemberType) { + case MemberTypes.Property: + PropertyInfo lopPi = target_type.GetProperty (lopParts[i]); + MethodInfo dstMi = lopPi.GetSetMethod (); + lopT = lopPi.PropertyType; + lopSetMbi = dstMi; + lopSetOC = OpCodes.Callvirt; + break; + case MemberTypes.Field: + FieldInfo dstFi = target_type.GetField(lopParts[i]); + lopT = dstFi.FieldType; + lopSetMbi = dstFi; + lopSetOC = OpCodes.Stfld; + break; + default: + throw new Exception (string.Format("GOML:member type not handle: {0}", lopParts[i])); + } + #endregion + + #region RIGHT OPERANDES + if (rop.StartsWith("\'")){ + if (!rop.EndsWith("\'")) + throw new Exception (string.Format + ("GOML:malformed string constant in handler: {0}", rop)); + string strcst = rop.Substring (1, rop.Length - 2); + + il.Emit(OpCodes.Ldstr,strcst); + + }else{ + if (lopT.IsEnum) + throw new NotImplementedException(); + + MethodInfo lopParseMi = lopT.GetMethod("Parse"); + if (lopParseMi == null) + throw new Exception (string.Format + ("GOML:no parse method found in: {0}", lopT.Name)); + il.Emit(OpCodes.Ldstr, rop); + il.Emit(OpCodes.Callvirt, lopParseMi); + il.Emit(OpCodes.Unbox_Any, lopT); + } + + #endregion + + //emit left operand assignment + il.Emit(lopSetOC, lopSetMbi); + } + + il.Emit(OpCodes.Ret); + + #endregion + + Delegate del = dm.CreateDelegate(binding.Target.Event.EventHandlerType, binding.Target.Instance); + MethodInfo addHandler = binding.Target.Event.GetAddMethod (); + addHandler.Invoke(binding.Target.Instance, new object[] {del}); + + binding.Resolved = true; + } + #region conversions internal static MethodInfo GetConvertMethod( Type targetType ) |