/* * DynamORM - Dynamic Object-Relational Mapping library. * Copyright (c) 2012-2015, Grzegorz Russek (grzegorz.russek@gmail.com) * All rights reserved. * * This code file is based on Kerosene ORM solution for parsing dynamic * lambda expressions by Moisés Barba Cebeira * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF * THE POSSIBILITY OF SUCH DAMAGE. */ using System; using System.Collections.Generic; using System.Dynamic; using System.Linq.Expressions; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Serialization; using System.Text; namespace DynamORM.Helpers.Dynamics { /// /// Class able to parse dynamic lambda expressions. Allows to create dynamic logic. /// public class DynamicParser : IExtendedDisposable { #region Node /// /// Generic bindable operation where some of its operands is a dynamic argument, or a dynamic member or /// a method of that argument. /// [Serializable] public class Node : IDynamicMetaObjectProvider, IFinalizerDisposable, ISerializable { private DynamicParser _parser = null; #region MetaNode /// /// Represents the dynamic binding and a binding logic of /// an object participating in the dynamic binding. /// internal class MetaNode : DynamicMetaObject { /// /// Initializes a new instance of the class. /// /// The parameter. /// The restrictions. /// The value. public MetaNode(Expression parameter, BindingRestrictions rest, object value) : base(parameter, rest, value) { } // Func was cool but caused memory leaks private DynamicMetaObject GetBinder(Node node) { Node o = (Node)this.Value; node.Parser = o.Parser; o.Parser.Last = node; ParameterExpression p = Expression.Variable(typeof(Node), "ret"); BlockExpression exp = Expression.Block(new ParameterExpression[] { p }, Expression.Assign(p, Expression.Constant(node))); return new MetaNode(exp, this.Restrictions, node); } /// /// Performs the binding of the dynamic get member operation. /// /// An instance of the that represents the details of the dynamic operation. /// /// The new representing the result of the binding. /// public override DynamicMetaObject BindGetMember(GetMemberBinder binder) { return GetBinder(new GetMember((Node)this.Value, binder.Name)); } /// /// Performs the binding of the dynamic set member operation. /// /// An instance of the that represents the details of the dynamic operation. /// The representing the value for the set member operation. /// /// The new representing the result of the binding. /// public override DynamicMetaObject BindSetMember(SetMemberBinder binder, DynamicMetaObject value) { return GetBinder(new SetMember((Node)this.Value, binder.Name, value.Value)); } /// /// Performs the binding of the dynamic get index operation. /// /// An instance of the that represents the details of the dynamic operation. /// An array of instances - indexes for the get index operation. /// /// The new representing the result of the binding. /// public override DynamicMetaObject BindGetIndex(GetIndexBinder binder, DynamicMetaObject[] indexes) { return GetBinder(new GetIndex((Node)this.Value, MetaList2List(indexes))); } /// /// Performs the binding of the dynamic set index operation. /// /// An instance of the that represents the details of the dynamic operation. /// An array of instances - indexes for the set index operation. /// The representing the value for the set index operation. /// /// The new representing the result of the binding. /// public override DynamicMetaObject BindSetIndex(SetIndexBinder binder, DynamicMetaObject[] indexes, DynamicMetaObject value) { return GetBinder(new SetIndex((Node)this.Value, MetaList2List(indexes), value.Value)); } /// /// Performs the binding of the dynamic invoke operation. /// /// An instance of the that represents the details of the dynamic operation. /// An array of instances - arguments to the invoke operation. /// /// The new representing the result of the binding. /// public override DynamicMetaObject BindInvoke(InvokeBinder binder, DynamicMetaObject[] args) { return GetBinder(new Invoke((Node)this.Value, MetaList2List(args))); } /// /// Performs the binding of the dynamic invoke member operation. /// /// An instance of the that represents the details of the dynamic operation. /// An array of instances - arguments to the invoke member operation. /// /// The new representing the result of the binding. /// public override DynamicMetaObject BindInvokeMember(InvokeMemberBinder binder, DynamicMetaObject[] args) { return GetBinder(new Method((Node)this.Value, binder.Name, MetaList2List(args))); } /// /// Performs the binding of the dynamic binary operation. /// /// An instance of the that represents the details of the dynamic operation. /// An instance of the representing the right hand side of the binary operation. /// /// The new representing the result of the binding. /// public override DynamicMetaObject BindBinaryOperation(BinaryOperationBinder binder, DynamicMetaObject arg) { return GetBinder(new Binary((Node)this.Value, binder.Operation, arg.Value)); } /// /// Performs the binding of the dynamic unary operation. /// /// An instance of the that represents the details of the dynamic operation. /// /// The new representing the result of the binding. /// public override DynamicMetaObject BindUnaryOperation(UnaryOperationBinder binder) { Node o = (Node)this.Value; Unary node = new Unary(o, binder.Operation) { Parser = o.Parser }; o.Parser.Last = node; // If operation is 'IsTrue' or 'IsFalse', we will return false to keep the engine working... object ret = node; if (binder.Operation == ExpressionType.IsTrue) ret = (object)false; if (binder.Operation == ExpressionType.IsFalse) ret = (object)false; ParameterExpression p = Expression.Variable(ret.GetType(), "ret"); // the type is now obtained from "ret" BlockExpression exp = Expression.Block( new ParameterExpression[] { p }, Expression.Assign(p, Expression.Constant(ret))); // the expression is now obtained from "ret" return new MetaNode(exp, this.Restrictions, node); } /// /// Performs the binding of the dynamic conversion operation. /// /// An instance of the that represents the details of the dynamic operation. /// /// The new representing the result of the binding. /// public override DynamicMetaObject BindConvert(ConvertBinder binder) { Node o = (Node)this.Value; Convert node = new Convert(o, binder.ReturnType) { Parser = o.Parser }; o.Parser.Last = node; // Reducing the object to return if this is an assignment node... object ret = o; bool done = false; while (!done) { if (ret is SetMember) ret = ((SetMember)o).Value; else if (ret is SetIndex) ret = ((SetIndex)o).Value; else done = true; } // Creating an instance... if (binder.ReturnType == typeof(string)) ret = ret.ToString(); else { try { if (binder.ReturnType.IsNullableType()) ret = null; // to avoid cast exceptions else ret = Activator.CreateInstance(binder.ReturnType, true); // true to allow non-public ctor as well } catch { // as the last resort scenario ret = new object(); } } ParameterExpression p = Expression.Variable(binder.ReturnType, "ret"); BlockExpression exp = Expression.Block( new ParameterExpression[] { p }, Expression.Assign(p, Expression.Constant(ret, binder.ReturnType))); // specifying binder.ReturnType return new MetaNode(exp, this.Restrictions, node); } private static object[] MetaList2List(DynamicMetaObject[] metaObjects) { if (metaObjects == null) return null; object[] list = new object[metaObjects.Length]; for (int i = 0; i < metaObjects.Length; i++) list[i] = metaObjects[i].Value; return list; } } #endregion MetaNode #region Argument /// /// Describe a dynamic argument used in a dynamic lambda expression. /// [Serializable] public class Argument : Node, ISerializable { /// /// Initializes a new instance of the class. /// /// The name. public Argument(string name) : base(name) { } /// /// Initializes a new instance of the class. /// /// The info. /// The context. protected Argument(SerializationInfo info, StreamingContext context) : base(info, context) { } /// Returns a that represents this instance. /// A that represents this instance. public override string ToString() { if (IsDisposed) return "{DynamicParser::Node::Argument::Disposed}"; return Name; } } #endregion Argument #region GetMember /// /// Describe a 'get member' operation, as in 'x => x.Member'. /// [Serializable] public class GetMember : Node, ISerializable { /// /// Initializes a new instance of the class. /// /// The host. /// The name. public GetMember(Node host, string name) : base(host, name) { } /// /// Initializes a new instance of the class. /// /// The info. /// The context. protected GetMember(SerializationInfo info, StreamingContext context) : base(info, context) { } /// Returns a that represents this instance. /// A that represents this instance. public override string ToString() { if (IsDisposed) return "{DynamicParser::Node::GetMember::Disposed}"; return string.Format("{0}.{1}", Host.Sketch(), Name.Sketch()); } } #endregion GetMember #region SetMember /// /// Describe a 'set member' operation, as in 'x => x.Member = y'. /// [Serializable] public class SetMember : Node, ISerializable { /// /// Gets the value that has been (virtually) assigned to this member. It might be null if the null value has been /// assigned to this instance, or if this instance is disposed. /// public object Value { get; private set; } /// /// Initializes a new instance of the class. /// /// The host. /// The name. /// The value. public SetMember(Node host, string name, object value) : base(host, name) { Value = value; } /// /// Initializes a new instance of the class. /// /// The info. /// The context. protected SetMember(SerializationInfo info, StreamingContext context) : base(info, context) { string type = info.GetString("MemberType"); Value = type == "NULL" ? null : info.GetValue("MemberValue", Type.GetType(type)); } /// /// Gets the object data. /// /// The info. /// The context. public override void GetObjectData(SerializationInfo info, StreamingContext context) { info.AddValue("MemberType", Value == null ? "NULL" : Value.GetType().AssemblyQualifiedName); if (Value != null) info.AddValue("MemberValue", Value); base.GetObjectData(info, context); } /// Returns a that represents this instance. /// A that represents this instance. public override string ToString() { if (IsDisposed) return "{DynamicParser::Node::SetMember::Disposed}"; return string.Format("({0}.{1} = {2})", Host.Sketch(), Name.Sketch(), Value.Sketch()); } /// Performs application-defined tasks associated with /// freeing, releasing, or resetting unmanaged resources. /// If set to true dispose object. public override void Dispose(bool disposing) { if (disposing) { try { if (Value != null) { var node = Value as Node; if (node != null) { if (node.IsNodeAncestor(this)) node.Host = null; node.Dispose(disposing); } Value = null; } } catch { } } base.Dispose(disposing); } } #endregion SetMember #region GetIndex /// /// Describe a 'get indexed' operation, as in 'x => x.Member[...]'. /// [Serializable] public class GetIndex : Node, ISerializable { /// Gets the indexes. public object[] Indexes { get; internal set; } /// /// Initializes a new instance of the class. /// /// The host. /// The indexes. /// Indexes array cannot be null. /// Indexes array cannot be empty. public GetIndex(Node host, object[] indexes) : base(host) { if (indexes == null) throw new ArgumentNullException("indexes", "Indexes array cannot be null."); if (indexes.Length == 0) throw new ArgumentException("Indexes array cannot be empty."); Indexes = indexes; } /// /// Initializes a new instance of the class. /// /// The info. /// The context. protected GetIndex(SerializationInfo info, StreamingContext context) : base(info, context) { int count = (int)info.GetValue("IndexCount", typeof(int)); if (count != 0) { Indexes = new object[count]; for (int i = 0; i < count; i++) { string typeName = info.GetString("IndexType" + i); object obj = typeName == "NULL" ? null : info.GetValue("IndexValue" + i, Type.GetType(typeName)); Indexes[i] = obj; } } } /// /// Gets the object data. /// /// The info. /// The context. public override void GetObjectData(SerializationInfo info, StreamingContext context) { int count = Indexes == null ? 0 : Indexes.Length; info.AddValue("IndexCount", count); for (int i = 0; i < count; i++) { info.AddValue("IndexType" + i, Indexes[i] == null ? "NULL" : Indexes[i].GetType().AssemblyQualifiedName); if (Indexes[i] != null) info.AddValue("IndexValue" + i, Indexes[i]); } base.GetObjectData(info, context); } /// Returns a that represents this instance. /// A that represents this instance. public override string ToString() { if (IsDisposed) return "{DynamicParser::Node::GetIndex::Disposed}"; return string.Format("{0}{1}", Host.Sketch(), Indexes == null ? "[empty]" : Indexes.Sketch()); } /// Performs application-defined tasks associated with /// freeing, releasing, or resetting unmanaged resources. /// If set to true dispose object. public override void Dispose(bool disposing) { if (disposing) { try { if (Indexes != null) { for (int i = 0; i < Indexes.Length; i++) { var node = Indexes[i] as Node; if (node != null) { if (node.IsNodeAncestor(this)) node.Host = null; node.Dispose(disposing); } } Array.Clear(Indexes, 0, Indexes.Length); } } catch { } Indexes = null; } base.Dispose(disposing); } } #endregion GetIndex #region SetIndex /// /// Describe a 'set indexed' operation, as in 'x => x.Member[...] = Value'. /// [Serializable] public class SetIndex : GetIndex, ISerializable { /// /// Gets the value that has been (virtually) assigned to this member. It might be null if the null value has been /// assigned to this instance, or if this instance is disposed. /// public object Value { get; private set; } /// /// Initializes a new instance of the class. /// /// The host. /// The indexes. /// The value. public SetIndex(Node host, object[] indexes, object value) : base(host, indexes) { Value = value; } /// /// Initializes a new instance of the class. /// /// The info. /// The context. protected SetIndex(SerializationInfo info, StreamingContext context) : base(info, context) { string type = info.GetString("MemberType"); Value = type == "NULL" ? null : info.GetValue("MemberValue", Type.GetType(type)); } /// /// Gets the object data. /// /// The info. /// The context. public override void GetObjectData(SerializationInfo info, StreamingContext context) { info.AddValue("MemberType", Value == null ? "NULL" : Value.GetType().AssemblyQualifiedName); if (Value != null) info.AddValue("MemberValue", Value); base.GetObjectData(info, context); } /// Returns a that represents this instance. /// A that represents this instance. public override string ToString() { if (IsDisposed) return "{DynamicParser::Node::SetIndex::Disposed}"; return string.Format("({0}{1} = {2})", Host.Sketch(), Indexes == null ? "[empty]" : Indexes.Sketch(), Value.Sketch()); } /// Performs application-defined tasks associated with /// freeing, releasing, or resetting unmanaged resources. /// If set to true dispose object. public override void Dispose(bool disposing) { if (disposing) { try { if (Value != null) { var node = Value as Node; if (node != null) { if (node.IsNodeAncestor(this)) node.Host = null; node.Dispose(disposing); } Value = null; } } catch { } } base.Dispose(disposing); } } #endregion SetIndex #region Invoke /// /// Describe a method invocation operation, as in 'x => x.Method(...)". /// [Serializable] public class Invoke : Node, ISerializable { /// Gets the arguments. public object[] Arguments { get; internal set; } /// /// Initializes a new instance of the class. /// /// The host. /// The arguments. public Invoke(Node host, object[] arguments) : base(host) { Arguments = arguments == null || arguments.Length == 0 ? null : arguments; } /// /// Initializes a new instance of the class. /// /// The info. /// The context. protected Invoke(SerializationInfo info, StreamingContext context) : base(info, context) { int count = (int)info.GetValue("ArgumentCount", typeof(int)); if (count != 0) { Arguments = new object[count]; for (int i = 0; i < count; i++) { string typeName = info.GetString("ArgumentType" + i); object obj = typeName == "NULL" ? null : info.GetValue("ArgumentValue" + i, Type.GetType(typeName)); Arguments[i] = obj; } } } /// /// Gets the object data. /// /// The info. /// The context. public override void GetObjectData(SerializationInfo info, StreamingContext context) { int count = Arguments == null ? 0 : Arguments.Length; info.AddValue("ArgumentCount", count); for (int i = 0; i < count; i++) { info.AddValue("ArgumentType" + i, Arguments[i] == null ? "NULL" : Arguments[i].GetType().AssemblyQualifiedName); if (Arguments[i] != null) info.AddValue("ArgumentValue" + i, Arguments[i]); } base.GetObjectData(info, context); } /// Performs application-defined tasks associated with /// freeing, releasing, or resetting unmanaged resources. /// If set to true dispose object. public override void Dispose(bool disposing) { if (disposing) { try { if (Arguments != null) { for (int i = 0; i < Arguments.Length; i++) { var node = Arguments[i] as Node; if (node != null) { if (node.IsNodeAncestor(this)) node.Host = null; node.Dispose(disposing); } } Array.Clear(Arguments, 0, Arguments.Length); } } catch { } Arguments = null; } base.Dispose(disposing); } /// Returns a that represents this instance. /// A that represents this instance. public override string ToString() { if (IsDisposed) return "{DynamicParser::Node::Invoke::Disposed}"; return string.Format("{0}{1}", Host.Sketch(), Arguments == null ? "()" : Arguments.Sketch(brackets: "()".ToCharArray())); } } #endregion Invoke #region Method /// /// Describe a method invocation operation, as in 'x => x.Method(...)". /// [Serializable] public class Method : Node, ISerializable { /// Gets the arguments. public object[] Arguments { get; internal set; } /// /// Initializes a new instance of the class. /// /// The host. /// The name. /// The arguments. public Method(Node host, string name, object[] arguments) : base(host, name) { Arguments = arguments == null || arguments.Length == 0 ? null : arguments; } /// /// Initializes a new instance of the class. /// /// The info. /// The context. protected Method(SerializationInfo info, StreamingContext context) : base(info, context) { int count = (int)info.GetValue("ArgumentCount", typeof(int)); if (count != 0) { Arguments = new object[count]; for (int i = 0; i < count; i++) { string typeName = info.GetString("ArgumentType" + i); object obj = typeName == "NULL" ? null : info.GetValue("ArgumentValue" + i, Type.GetType(typeName)); Arguments[i] = obj; } } } /// /// Gets the object data. /// /// The info. /// The context. public override void GetObjectData(SerializationInfo info, StreamingContext context) { int count = Arguments == null ? 0 : Arguments.Length; info.AddValue("ArgumentCount", count); for (int i = 0; i < count; i++) { info.AddValue("ArgumentType" + i, Arguments[i] == null ? "NULL" : Arguments[i].GetType().AssemblyQualifiedName); if (Arguments[i] != null) info.AddValue("ArgumentValue" + i, Arguments[i]); } base.GetObjectData(info, context); } /// Returns a that represents this instance. /// A that represents this instance. public override string ToString() { if (IsDisposed) return "{DynamicParser::Node::Method::Disposed}"; return string.Format("{0}.{1}{2}", Host.Sketch(), Name.Sketch(), Arguments == null ? "()" : Arguments.Sketch(brackets: "()".ToCharArray())); } /// Performs application-defined tasks associated with /// freeing, releasing, or resetting unmanaged resources. /// If set to true dispose object. public override void Dispose(bool disposing) { if (disposing) { try { if (Arguments != null) { for (int i = 0; i < Arguments.Length; i++) { var node = Arguments[i] as Node; if (node != null) { if (node.IsNodeAncestor(this)) node.Host = null; node.Dispose(disposing); } } Array.Clear(Arguments, 0, Arguments.Length); } } catch { } Arguments = null; } base.Dispose(disposing); } } #endregion Method #region Binary /// /// Represents a binary operation between a dynamic element and an arbitrary object, including null ones, as in /// 'x => (x && null)'. The left operand must be an instance of , whereas the right one /// can be any object, including null values. /// [Serializable] public class Binary : Node, ISerializable { /// Gets the operation. public ExpressionType Operation { get; private set; } /// Gets host of the . public Node Left { get { return Host; } } /// Gets the right side value. public object Right { get; private set; } /// /// Initializes a new instance of the class. /// /// The left. /// The operation. /// The right. public Binary(Node left, ExpressionType operation, object right) : base(left) { Operation = operation; Right = right; } /// /// Initializes a new instance of the class. /// /// The info. /// The context. protected Binary(SerializationInfo info, StreamingContext context) : base(info, context) { Operation = (ExpressionType)info.GetValue("Operation", typeof(ExpressionType)); string type = info.GetString("RightType"); Right = type == "NULL" ? null : (Node)info.GetValue("RightItem", Type.GetType(type)); } /// /// Gets the object data. /// /// The info. /// The context. public override void GetObjectData(SerializationInfo info, StreamingContext context) { info.AddValue("Operation", Operation); info.AddValue("RightType", Right == null ? "NULL" : Right.GetType().AssemblyQualifiedName); if (Right != null) info.AddValue("RightItem", Right); base.GetObjectData(info, context); } /// Returns a that represents this instance. /// A that represents this instance. public override string ToString() { if (IsDisposed) return "{DynamicParser::Node::Binary::Disposed}"; return string.Format("({0} {1} {2})", Host.Sketch(), Operation, Right.Sketch()); } /// Performs application-defined tasks associated with /// freeing, releasing, or resetting unmanaged resources. /// If set to true dispose object. public override void Dispose(bool disposing) { base.Dispose(disposing); if (disposing) { if (Left != null) { if (Left is Node) { Node n = (Node)Left; if (!n.IsDisposed) n.Dispose(disposing); } } if (Right != null) { if (Right is Node) { Node n = (Node)Right; if (!n.IsDisposed) n.Dispose(disposing); } Right = null; } } } } #endregion Binary #region Unary /// /// Represents an unary operation, as in 'x => !x'. The target must be a instance. There /// is no distinction between pre- and post- version of the same operation. /// [Serializable] public class Unary : Node, ISerializable { /// Gets the operation. public ExpressionType Operation { get; private set; } /// Gets host of the . public Node Target { get; private set; } /// /// Initializes a new instance of the class. /// /// The target. /// The operation. public Unary(Node target, ExpressionType operation) : base(target) { Operation = operation; Target = target; } /// /// Initializes a new instance of the class. /// /// The info. /// The context. protected Unary(SerializationInfo info, StreamingContext context) : base(info, context) { Operation = (ExpressionType)info.GetValue("Operation", typeof(ExpressionType)); } /// /// Gets the object data. /// /// The info. /// The context. public override void GetObjectData(SerializationInfo info, StreamingContext context) { info.AddValue("Operation", Operation); base.GetObjectData(info, context); } /// Returns a that represents this instance. /// A that represents this instance. public override string ToString() { if (IsDisposed) return "{DynamicParser::Node::Binary::Disposed}"; return string.Format("({0} {1})", Operation, Host.Sketch()); } /// Performs application-defined tasks associated with /// freeing, releasing, or resetting unmanaged resources. /// If set to true dispose object. public override void Dispose(bool disposing) { if (disposing) { try { if (Target != null) { var node = Target as Node; if (node != null) { if (node.IsNodeAncestor(this)) node.Host = null; node.Dispose(disposing); } Target = null; } } catch { } } base.Dispose(disposing); } } #endregion Unary #region Convert /// /// Represents a conversion operation, as in 'x => (string)x'. /// [Serializable] public class Convert : Node, ISerializable { /// Gets the new type to which value will be converted. public Type NewType { get; private set; } /// Gets host of the . public Node Target { get { return Host; } } /// /// Initializes a new instance of the class. /// /// The target. /// The new type. public Convert(Node target, Type newType) : base(target) { NewType = newType; } /// /// Initializes a new instance of the class. /// /// The info. /// The context. protected Convert(SerializationInfo info, StreamingContext context) : base(info, context) { NewType = (Type)info.GetValue("NewType", typeof(Type)); } /// /// Gets the object data. /// /// The info. /// The context. public override void GetObjectData(SerializationInfo info, StreamingContext context) { info.AddValue("NewType", NewType); base.GetObjectData(info, context); } } #endregion Convert /// /// Gets the name of the member. It might be null if this instance is disposed. /// public string Name { get; internal set; } /// Gets host of the . public Node Host { get; internal set; } /// Gets reference to the parser. public DynamicParser Parser { get { return _parser; } internal set { _parser = value; if (_parser != null) _parser._allNodes.Add(this); } } /// /// Initializes a new instance of the class. /// internal Node() { IsDisposed = false; } /// /// Initializes a new instance of the class. /// /// The host. internal Node(Node host) : this() { if (host == null) throw new ArgumentNullException("host", "Host cannot be null."); Host = host; } /// /// Initializes a new instance of the class. /// /// The name. internal Node(string name) : this() { Name = name.Validated("Name"); } /// /// Initializes a new instance of the class. /// /// The host. /// The name. /// Host cannot be null. internal Node(Node host, string name) : this(host) { Name = name.Validated("Name"); } /// /// Initializes a new instance of the class. /// /// The info. /// The context. protected Node(SerializationInfo info, StreamingContext context) { Name = info.GetString("MemberName"); string type = info.GetString("HostType"); Host = type == "NULL" ? null : (Node)info.GetValue("HostItem", Type.GetType(type)); } /// Returns whether the given node is an ancestor of this instance. /// The node to test. /// True if the given node is an ancestor of this instance. public bool IsNodeAncestor(Node node) { if (node != null) { Node parent = Host; while (parent != null) { if (object.ReferenceEquals(parent, node)) return true; parent = parent.Host; } } return false; } /// Returns a that represents this instance. /// A that represents this instance. public override string ToString() { if (IsDisposed) return "{DynamicParser::Node::Disposed}"; return "{DynamicParser::Node::Empty}"; } #region Implementation of IDynamicMetaObjectProvider /// Returns the responsible /// for binding operations performed on this object. /// The expression tree representation of the runtime value. /// The to bind this object. /// Thrown if this instance is disposed. public DynamicMetaObject GetMetaObject(System.Linq.Expressions.Expression parameter) { if (IsDisposed) throw new ObjectDisposedException("DynamicParser.Node"); return new MetaNode( parameter, BindingRestrictions.GetInstanceRestriction(parameter, this), this); } #endregion Implementation of IDynamicMetaObjectProvider #region Implementation of IFinalizerDisposable /// Finalizes an instance of the class. ~Node() { Dispose(false); } /// Gets a value indicating whether this instance is disposed. public bool IsDisposed { get; private set; } /// Performs application-defined tasks associated with /// freeing, releasing, or resetting unmanaged resources. public virtual void Dispose() { Dispose(true); GC.SuppressFinalize(this); } /// Performs application-defined tasks associated with /// freeing, releasing, or resetting unmanaged resources. /// If set to true dispose object. public virtual void Dispose(bool disposing) { if (disposing) { IsDisposed = true; if (Host != null && !Host.IsDisposed) Host.Dispose(); Host = null; Parser = null; } } #endregion Implementation of IFinalizerDisposable #region Implementation of ISerializable /// /// Populates a with the data needed to serialize the target object. /// /// The to populate with data. /// The destination (see ) for this serialization. public virtual void GetObjectData(SerializationInfo info, StreamingContext context) { if (!string.IsNullOrEmpty(Name)) info.AddValue("MemberName", Name); info.AddValue("HostType", Host == null ? "NULL" : Host.GetType().AssemblyQualifiedName); if (Host != null) info.AddValue("HostItem", Host); } #endregion Implementation of ISerializable } #endregion Node #region Data private List _arguments = new List(); private List _allNodes = new List(); private object _uncertainResult; #endregion Data #region Properties /// Gets the last node (root of the tree). public Node Last { get; internal set; } /// /// Gets an enumeration containing the dynamic arguments used in the dynamic lambda expression parsed. /// public IEnumerable Arguments { get { List list = new List(); if (!IsDisposed && _arguments != null) list.AddRange(_arguments); foreach (Node.Argument arg in list) yield return arg; list.Clear(); list = null; } } /// /// Gets the number of dynamic arguments used in the dynamic lambda expression parsed. /// public int Count { get { return _arguments == null ? 0 : _arguments.Count; } } /// /// Gets the result of the parsing of the dynamic lambda expression. This result can be either an arbitrary object, /// including null, if the expression resolves to it, or an instance of the class that /// contains the last logic expression evaluated when parsing the dynamic lambda expression. /// public object Result { get { return _uncertainResult ?? Last; } } #endregion Properties private DynamicParser(Delegate f) { // I know this can be almost a one liner // but it causes memory leaks when so. ParameterInfo[] pars = f.Method.GetParameters(); foreach (ParameterInfo p in pars) { int attrs = p.GetCustomAttributes(typeof(DynamicAttribute), true).Length; if (attrs != 0) { Node.Argument par = new Node.Argument(p.Name) { Parser = this }; this._arguments.Add(par); } else throw new ArgumentException(string.Format("Argument '{0}' must be dynamic.", p.Name)); } try { _uncertainResult = f.DynamicInvoke(_arguments.ToArray()); } catch (TargetInvocationException e) { if (e.InnerException != null) throw e.InnerException; else throw e; } } /// /// Parses the dynamic lambda expression given in the form of a delegate, and returns a new instance of the /// class that holds the dynamic arguments used in the dynamic lambda expression, and /// the result of the parsing. /// /// The dynamic lambda expression to parse. /// A new instance of . public static DynamicParser Parse(Delegate f) { return new DynamicParser(f); } /// Returns a that represents this instance. /// A that represents this instance. public override string ToString() { if (IsDisposed) return "{DynamicParser::Disposed}"; StringBuilder sb = new StringBuilder(); sb.Append("("); bool first = true; if (_arguments != null) { foreach (Node.Argument arg in _arguments) { if (!first) sb.Append(", "); else first = false; sb.Append(arg); } } sb.Append(")"); sb.AppendFormat(" => {0}", Result.Sketch()); return sb.ToString(); } #region Implementation of IExtendedDisposable /// Gets a value indicating whether this instance is disposed. public bool IsDisposed { get; private set; } /// /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. /// public void Dispose() { IsDisposed = true; if (_uncertainResult != null) { if (_uncertainResult is Node) ((Node)_uncertainResult).Dispose(); _uncertainResult = null; } if (Last != null) { if (!Last.IsDisposed) Last.Dispose(); Last = null; } if (_arguments != null) { _arguments.ForEach(x => { if (!x.IsDisposed) x.Dispose(); }); _arguments.Clear(); _arguments = null; } if (_allNodes != null) { _allNodes.ForEach(x => { if (!x.IsDisposed) x.Dispose(); }); _allNodes.Clear(); _allNodes = null; } } #endregion Implementation of IExtendedDisposable } }