]> O.S.I.I.S - jp/crow.git/commitdiff
new CompileDynEventHandler method with new 'BindingMember' class
authorJean-Philippe Bruyère <jp_bruyere@hotmail.com>
Mon, 23 Jan 2017 17:12:07 +0000 (18:12 +0100)
committerJean-Philippe Bruyère <jp_bruyere@hotmail.com>
Mon, 23 Jan 2017 17:12:07 +0000 (18:12 +0100)
Crow.csproj
src/CompilerServices/CompilerServices.cs
src/IML/BindingMember.cs [new file with mode: 0644]

index d407629016e6bdc36a27ab526049ea421c311f38..709c7c41e3a1fe6236e2d22a2368ea42d603a7d9 100644 (file)
     <Compile Include="src\GraphicObjects\SaturationValueSelector.cs" />
     <Compile Include="src\IML\EventBinding.cs" />
     <Compile Include="src\PerformanceMeasure.cs" />
+    <Compile Include="src\IML\BindingMember.cs" />
   </ItemGroup>
   <ItemGroup>
     <Reference Include="System" />
index c626a6e2092f007f88762c145d06c71a7f2f9f79..dc2f7a1c70430a69ffe273d7511dff687d2ff0c1 100644 (file)
@@ -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);
+               /// <summary>
+               /// MSIL helper, go n levels up
+               /// </summary>
+               /// <returns><c>true</c>, if logical parents are not null</returns>
+               /// <param name="instance">Start Instance</param>
+               /// <param name="levelCount">Levels to go upward</param>
+               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;
                }
                /// <summary>
                /// 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 (file)
index 0000000..1bab595
--- /dev/null
@@ -0,0 +1,152 @@
+//
+//  BindingMember.cs
+//
+//  Author:
+//       Jean-Philippe Bruyère <jp.bruyere@hotmail.com>
+//
+//  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 <http://www.gnu.org/licenses/>.
+using System;
+using System.Reflection.Emit;
+
+namespace Crow
+{
+       /// <summary>
+       /// 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
+       /// </summary>
+       public class BindingMember
+       {
+               /// <summary>
+               /// true if expression was enclosed in '
+               /// </summary>
+               public bool IsStringConstant = false;
+               /// <summary>
+               /// Nb level to go up, '-1' for template root
+               /// </summary>
+               public int LevelsUp;
+               /// <summary>
+               /// Remaining string after '/' split, splitted on '.'
+               /// </summary>
+               public string[] Tokens;
+
+               /// <summary>
+               /// Target the template's root node, expression was in the form './name[.name[...]]' or '/name[.name[...]]'
+               /// </summary>
+               public bool IsTemplateBinding { get { return LevelsUp < 0; }}
+
+               /// <summary>
+               /// No level change and expression was '.name'
+               /// </summary>
+               /// <value><c>true</c> if this instance is current node property; otherwise, <c>false</c>.</value>
+               public bool IsCurrentNodeProperty {
+                       get {
+                               return LevelsUp == 0 && ( Tokens.Length == 2 && string.IsNullOrEmpty(Tokens[0]));
+                       }
+               }
+               /// <summary>
+               /// no level change, and only a single name in Tokens[], that's dataSource member if property binding
+               /// </summary>
+               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);
+               }
+       }
+}
+