From fb73ce236b57ee5a58f498c3c4236e003436d921 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Jean-Philippe=20Bruy=C3=A8re?= Date: Mon, 23 Jan 2017 18:12:07 +0100 Subject: [PATCH] new CompileDynEventHandler method with new 'BindingMember' class --- Crow.csproj | 1 + src/CompilerServices/CompilerServices.cs | 259 ++++++----------------- src/IML/BindingMember.cs | 152 +++++++++++++ 3 files changed, 218 insertions(+), 194 deletions(-) create mode 100644 src/IML/BindingMember.cs diff --git a/Crow.csproj b/Crow.csproj index d4076290..709c7c41 100644 --- a/Crow.csproj +++ b/Crow.csproj @@ -152,6 +152,7 @@ + diff --git a/src/CompilerServices/CompilerServices.cs b/src/CompilerServices/CompilerServices.cs index c626a6e2..dc2f7a1c 100644 --- a/src/CompilerServices/CompilerServices.cs +++ b/src/CompilerServices/CompilerServices.cs @@ -42,6 +42,9 @@ namespace Crow internal static MethodInfo miCreateDel = typeof(CompilerServices).GetMethod ("createDel", BindingFlags.Static | BindingFlags.NonPublic); internal static MethodInfo miGetImplOp = typeof(CompilerServices).GetMethod ("getImplicitOp", BindingFlags.Static | BindingFlags.NonPublic); + + internal static MethodInfo miGoUpLevels = typeof(CompilerServices).GetMethod("goUpNbLevels", BindingFlags.Static | BindingFlags.NonPublic); + internal static FieldInfo fiCachedDel = typeof(Instantiator).GetField("cachedDelegates", BindingFlags.Instance | BindingFlags.NonPublic); internal static FieldInfo fiTemplateBinding = typeof(Instantiator).GetField("templateBinding", BindingFlags.Instance | BindingFlags.NonPublic); internal static MethodInfo miDSChangeEmitHelper = typeof(Instantiator).GetMethod("dataSourceChangedEmitHelper", BindingFlags.Instance | BindingFlags.NonPublic); @@ -602,7 +605,7 @@ namespace Crow return Delegate.CreateDelegate (eventType, instance, mi); } - public static Delegate compileDynEventHandler2(EventInfo sourceEvent, string expression, NodeAddress currentNode = null){ + internal static Delegate compileDynEventHandler(EventInfo sourceEvent, string expression, NodeAddress currentNode = null){ #if DEBUG_BINDING Debug.WriteLine ("\tCompile Event {0}: {1}", sourceEvent.Name, expression); #endif @@ -630,95 +633,71 @@ namespace Crow string [] srcLines = expression.Trim ().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 - { - //maybe we could handle here handler function name + if (string.IsNullOrEmpty (srcLine)) continue; - } + string [] operandes = srcLine.Trim ().Split (new char [] { '=' }); + if (operandes.Length != 2) //not an affectation + throw new NotSupportedException (); - string rop = operandes [operandes.Length - 1].Trim (); + System.Reflection.Emit.Label cancel = il.DefineLabel (); + System.Reflection.Emit.Label cancelFinalSet = il.DefineLabel (); + System.Reflection.Emit.Label success = il.DefineLabel (); - #region LEFT OPERANDES - string [] lopParts = operandes [0].Trim ().Split ('.'); - MemberInfo lopMI = null; + BindingMember lop = new BindingMember (operandes [0].Trim ()); + BindingMember rop = new BindingMember (operandes [1].Trim ()); il.Emit (OpCodes.Ldarg_0); //load sender ref onto the stack, the current node - if (lopParts.Length > 1) { - NodeAddress lopNA = currentNode.ResolveExpression (ref operandes [0]); - CompilerServices.emitGetInstance (il, currentNode, lopNA); - lopType = lopNA.NodeType; - } - - string [] bindTrg = lopParts.Last().Split ('.'); - - if (bindTrg.Length == 1) - lopMI = lopType.GetMember (bindTrg [0]).FirstOrDefault(); - else if (bindTrg.Length == 2) { - //named target - //TODO: - il.Emit(OpCodes.Ldstr, bindTrg[0]); - il.Emit(OpCodes.Callvirt, miFindByName); - lopMI = lopType.GetMember (bindTrg [1]).FirstOrDefault(); - } else - throw new Exception ("Syntax error in binding, expected 'go dot member'"); - - - if (lopMI == null) - throw new Exception (string.Format ("IML BINDING: Member not found")); + #region Left operande + PropertyInfo lopPI = null; - OpCode lopSetOpCode; - dynamic lopSetMI; - Type lopT = null; - switch (lopMI.MemberType) { - case MemberTypes.Property: - lopSetOpCode = OpCodes.Callvirt; - PropertyInfo lopPi = lopMI as PropertyInfo; - lopT = lopPi.PropertyType; - lopSetMI = lopPi.GetSetMethod (); - break; - case MemberTypes.Field: - lopSetOpCode = OpCodes.Stfld; - FieldInfo dstFi = lopMI as FieldInfo; - lopT = dstFi.FieldType; - lopSetMI = dstFi; - break; - default: - throw new Exception (string.Format ("GOML:member type not handle")); - } + //in dyn handler, no datasource binding, so single name in expression are also handled as current node property + if (lop.IsSingleName) + lopPI = lopType.GetProperty (lop.Tokens [0]); + else if (lop.IsCurrentNodeProperty) + lopPI = lopType.GetProperty (lop.Tokens [1]); + else + lop.emitGetTarget (il, cancel); #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 (rop.StartsWith ("this",StringComparison.OrdinalIgnoreCase)){ - il.Emit (OpCodes.Ldarg_0); //load sender ref onto the stack - } else { - if (lopT.IsEnum) - throw new NotImplementedException (); + if (rop.IsStringConstant){ + il.Emit (OpCodes.Ldstr, rop.Tokens[0]); + lop.emitSetProperty (il, cancelFinalSet); + }else if (rop.LevelsUp ==0 && !string.IsNullOrEmpty(rop.Tokens[0])) {//parsable constant depending on lop type + //if left operand is member of current node, it's easy to fetch type, else we should use reflexion in msil + if (lopPI == null){//accept GraphicObj members, but it's restricive + //TODO: we should get the parse method by reflexion, or something else + lopPI = typeof(GraphicObject).GetProperty (lop.Tokens [lop.Tokens.Length-1]); + if (lopPI == null) + throw new NotSupportedException (); + } - MethodInfo lopParseMi = lopT.GetMethod ("Parse"); + MethodInfo lopParseMi = lopPI.PropertyType.GetMethod ("Parse"); if (lopParseMi == null) throw new Exception (string.Format - ("GOML:no parse method found in: {0}", lopT.Name)); - il.Emit (OpCodes.Ldstr, rop); + ("IML: no static 'Parse' method found in: {0}", lopPI.PropertyType.Name)); + + il.Emit (OpCodes.Ldstr, operandes [1].Trim ()); il.Emit (OpCodes.Callvirt, lopParseMi); - il.Emit (OpCodes.Unbox_Any, lopT); + il.Emit (OpCodes.Unbox_Any, lopPI.PropertyType); + + //emit left operand assignment + il.Emit (OpCodes.Callvirt, lopPI.GetSetMethod()); + } else {//tree parsing and propert gets + rop.emitGetTarget (il, cancel); + rop.emitGetProperty (il, cancel); + lop.emitSetProperty (il, cancelFinalSet); } - #endregion - //emit left operand assignment - il.Emit (lopSetOpCode, lopSetMI); + il.Emit (OpCodes.Br, success); + + il.MarkLabel (cancelFinalSet); + il.Emit (OpCodes.Pop); //pop null MemberInfo on the stack causing cancelation + il.MarkLabel (cancel); + il.Emit (OpCodes.Pop); //pop null instance on the stack causing cancelation + il.MarkLabel (success); } il.Emit (OpCodes.Ret); @@ -726,128 +705,20 @@ namespace Crow return dm.CreateDelegate (sourceEvent.EventHandlerType); } - internal static Delegate compileDynEventHandler(EventInfo sourceEvent, string expression, NodeAddress currentNode = null){ - #if DEBUG_BINDING - Debug.WriteLine ("\tCompile Event {0}: {1}", sourceEvent.Name, expression); - #endif - - Type lopType = null; - - if (currentNode == null) - lopType = sourceEvent.DeclaringType;//TODO:double check if derived class could be returned - else - lopType = currentNode.NodeType; - - #region Retrieve EventHandler parameter type - MethodInfo evtInvoke = sourceEvent.EventHandlerType.GetMethod ("Invoke"); - ParameterInfo [] evtParams = evtInvoke.GetParameters (); - Type handlerArgsType = evtParams [1].ParameterType; - #endregion - - Type [] args = { CompilerServices.TObject, handlerArgsType }; - DynamicMethod dm = new DynamicMethod ("dyn_eventHandler", - typeof(void), - args, true); - ILGenerator il = dm.GetILGenerator (256); - il.Emit (OpCodes.Nop); - - string [] srcLines = expression.Trim ().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 - { - //maybe we could handle here handler function name - continue; - } - - string rop = operandes [operandes.Length - 1].Trim (); - - #region LEFT OPERANDES - string [] lopParts = operandes [0].Trim ().Split ('/'); - MemberInfo lopMI = null; - - il.Emit (OpCodes.Ldarg_0); //load sender ref onto the stack - - if (lopParts.Length > 1) { - NodeAddress lopNA = currentNode.ResolveExpression (ref operandes [0]); - CompilerServices.emitGetInstance (il, currentNode, lopNA); - lopType = lopNA.NodeType; - } - - string [] bindTrg = lopParts.Last().Split ('.'); - - if (bindTrg.Length == 1) - lopMI = lopType.GetMember (bindTrg [0]).FirstOrDefault(); - else if (bindTrg.Length == 2) { - //named target - //TODO: - il.Emit(OpCodes.Ldstr, bindTrg[0]); - il.Emit(OpCodes.Callvirt, miFindByName); - lopMI = lopType.GetMember (bindTrg [1]).FirstOrDefault(); - } else - throw new Exception ("Syntax error in binding, expected 'go dot member'"); - - - if (lopMI == null) - throw new Exception (string.Format ("IML BINDING: Member not found")); - - OpCode lopSetOpCode; - dynamic lopSetMI; - Type lopT = null; - switch (lopMI.MemberType) { - case MemberTypes.Property: - lopSetOpCode = OpCodes.Callvirt; - PropertyInfo lopPi = lopMI as PropertyInfo; - lopT = lopPi.PropertyType; - lopSetMI = lopPi.GetSetMethod (); - break; - case MemberTypes.Field: - lopSetOpCode = OpCodes.Stfld; - FieldInfo dstFi = lopMI as FieldInfo; - lopT = dstFi.FieldType; - lopSetMI = dstFi; - break; - default: - throw new Exception (string.Format ("GOML:member type not handle")); - } - #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 (rop.StartsWith ("this",StringComparison.OrdinalIgnoreCase)){ - il.Emit (OpCodes.Ldarg_0); //load sender ref onto the stack - } 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 (lopSetOpCode, lopSetMI); + /// + /// MSIL helper, go n levels up + /// + /// true, if logical parents are not null + /// Start Instance + /// Levels to go upward + internal static ILayoutable goUpNbLevels(ILayoutable instance, int levelCount){ + ILayoutable tmp = instance; + int i = 0; + while (tmp != null && i < levelCount) { + tmp = tmp.LogicalParent; + i++; } - - il.Emit (OpCodes.Ret); - - return dm.CreateDelegate (sourceEvent.EventHandlerType); + return tmp; } /// /// Splits expression on semicolon but ignore those between accolades diff --git a/src/IML/BindingMember.cs b/src/IML/BindingMember.cs new file mode 100644 index 00000000..1bab5959 --- /dev/null +++ b/src/IML/BindingMember.cs @@ -0,0 +1,152 @@ +// +// BindingMember.cs +// +// Author: +// Jean-Philippe Bruyère +// +// Copyright (c) 2017 jp +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +using System; +using System.Reflection.Emit; + +namespace Crow +{ + /// + /// Expression token, a variable, a string constant or a parsable constant (having a static Parse method) + /// '../' => 1 level up in graphic tree + /// './' or '/' => template root level + /// '.Name1.Name2' current level properties + /// 'name.prop' named descendant in graphic tree, search with 'FindByName' method of GraphicObject + /// + public class BindingMember + { + /// + /// true if expression was enclosed in ' + /// + public bool IsStringConstant = false; + /// + /// Nb level to go up, '-1' for template root + /// + public int LevelsUp; + /// + /// Remaining string after '/' split, splitted on '.' + /// + public string[] Tokens; + + /// + /// Target the template's root node, expression was in the form './name[.name[...]]' or '/name[.name[...]]' + /// + public bool IsTemplateBinding { get { return LevelsUp < 0; }} + + /// + /// No level change and expression was '.name' + /// + /// true if this instance is current node property; otherwise, false. + public bool IsCurrentNodeProperty { + get { + return LevelsUp == 0 && ( Tokens.Length == 2 && string.IsNullOrEmpty(Tokens[0])); + } + } + /// + /// no level change, and only a single name in Tokens[], that's dataSource member if property binding + /// + public bool IsSingleName { get { return LevelsUp == 0 && Tokens.Length == 1; }} + + #region CTOR + public BindingMember (){} + public BindingMember (string expression){ + if (string.IsNullOrEmpty (expression)) + return; + + string[] splitedExp = expression.Trim().Split ('/'); + + int ptr = 0; + if (splitedExp.Length == 1) { + if (splitedExp [0].StartsWith ("\'")) { + if (!splitedExp [0].EndsWith ("\'")) + throw new Exception (string.Format + ("IML:malformed string constant in binding expression: {0}", splitedExp [0])); + Tokens = new string[] { splitedExp [0].Substring (1, splitedExp [0].Length - 2) }; + IsStringConstant = true; + return; + } + } else { + if (string.IsNullOrEmpty (splitedExp [0]) || splitedExp [0] == ".") {//template root + LevelsUp = -1; + ptr++; + } else { + while (splitedExp [ptr] == "..") + ptr++; + } + } + if (ptr != splitedExp.Length - 1) + throw new Exception ("invalid expresion: " + expression); + Tokens = splitedExp [ptr].Split ('.'); + } + #endregion + + public void emitGetTarget(ILGenerator il, System.Reflection.Emit.Label cancel){ + + if (IsTemplateBinding) { + System.Reflection.Emit.Label nextLogicParent = il.DefineLabel (); + il.MarkLabel (nextLogicParent); + il.Emit (OpCodes.Callvirt, CompilerServices.miGetLogicalParent); + il.Emit (OpCodes.Dup); + il.Emit (OpCodes.Brfalse, cancel); + il.Emit (OpCodes.Isinst, typeof(TemplatedControl)); + il.Emit (OpCodes.Dup); + il.Emit (OpCodes.Brfalse, nextLogicParent); + } else if (LevelsUp > 0) {//go upward in logical tree + il.Emit (OpCodes.Ldind_I4, LevelsUp);//push arg 2 of goUpLevels + il.Emit (OpCodes.Callvirt, CompilerServices.miGoUpLevels); + //test if null + il.Emit (OpCodes.Dup); + il.Emit (OpCodes.Brfalse, cancel); + } + + if (!string.IsNullOrEmpty (Tokens [0])) {//find by name + il.Emit (OpCodes.Ldstr, Tokens [0]); + il.Emit (OpCodes.Callvirt, CompilerServices.miFindByName); + il.Emit (OpCodes.Dup); + il.Emit (OpCodes.Brfalse, cancel); + } + + for (int i = 1; i < Tokens.Length -1; i++) { + il.Emit (OpCodes.Ldstr, Tokens [i]);//load member name + il.Emit (OpCodes.Call, CompilerServices.miGetMembIinfoWithRefx); + il.Emit (OpCodes.Dup); + il.Emit (OpCodes.Brfalse, cancel); + il.Emit (OpCodes.Call, CompilerServices.miGetValWithRefx); + il.Emit (OpCodes.Dup); + il.Emit (OpCodes.Brfalse, cancel); + } + } + public void emitGetProperty(ILGenerator il, System.Reflection.Emit.Label cancel) { + il.Emit (OpCodes.Ldstr, Tokens [Tokens.Length -1]);//load member name + il.Emit (OpCodes.Call, CompilerServices.miGetMembIinfoWithRefx); + il.Emit (OpCodes.Dup); + il.Emit (OpCodes.Brfalse, cancel); + il.Emit (OpCodes.Call, CompilerServices.miGetValWithRefx); + } + public void emitSetProperty(ILGenerator il, System.Reflection.Emit.Label cancel) { + il.Emit (OpCodes.Ldstr, Tokens [Tokens.Length -1]);//load member name + il.Emit (OpCodes.Call, CompilerServices.miGetMembIinfoWithRefx); + il.Emit (OpCodes.Dup); + il.Emit (OpCodes.Brfalse, cancel); + il.Emit (OpCodes.Call, CompilerServices.miSetValWithRefx); + } + } +} + -- 2.47.3