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);
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
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);
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
--- /dev/null
+//
+// 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);
+ }
+ }
+}
+