Click here to Skip to main content
15,886,518 members
Articles / Programming Languages / C#

CodeDOM Classes for Solution and Project Files (Part 5)

Rate me:
Please Sign up or sign in to vote.
5.00/5 (12 votes)
30 Nov 2012CDDL7 min read 29.9K   1.2K   18  
CodeDOM objects for VS Solution and Project files.
// The Nova Project by Ken Beckett.
// Copyright (C) 2007-2012 Inevitable Software, all rights reserved.
// Released under the Common Development and Distribution License, CDDL-1.0: http://opensource.org/licenses/cddl1.php

using Nova.Parsing;
using Nova.Rendering;

namespace Nova.CodeDOM
{
    /// <summary>
    /// Represents an un-named method that can be assigned to a delegate.
    /// </summary>
    /// <remarks>
    /// An AnonymousMethod distinguishes between having zero parameters and unspecified parameters (no parens
    /// at all).  If the parameters are unspecified, the anonymous method is convertible to a delegate with any
    /// parameter list not containing out parameters.  It's possible that the empty parens will be needed in
    /// some cases to prevent ambiguity.
    /// </remarks>
    public class AnonymousMethod : Expression, IParameters, IBlock
    {
        #region /* FIELDS */

        protected ChildList<ParameterDecl> _parameters;
        protected Block _body;

        #endregion

        #region /* CONSTRUCTORS */

        /// <summary>
        /// Create an <see cref="AnonymousMethod"/>.
        /// </summary>
        public AnonymousMethod(CodeObject body, params ParameterDecl[] parameters)
        {
            // Allow derived classes to pass any non-Block code object, in which case it will
            // be wrapped in a Block.
            Body = ((body == null || body is Block) ? (Block)body : new Block(body));

            // Allow the parameter collection to be null (unspecified parameters) if null is specifically passed in (for AnonymousMethod)
            if (parameters != null)
                CreateParameters().AddRange(parameters);
        }

        /// <summary>
        /// Create an <see cref="AnonymousMethod"/>.
        /// </summary>
        public AnonymousMethod(params ParameterDecl[] parameters)
            : this(new Block(), parameters)
        { }

        #endregion

        #region /* PROPERTIES */

        /// <summary>
        /// The <see cref="Block"/> body.
        /// </summary>
        public Block Body
        {
            get { return _body; }
            set
            {
                _body = value;
                if (_body != null)
                {
                    _body.Parent = this;
                    ReformatBlock();
                }
            }
        }

        /// <summary>
        /// The parameters of the anonymous method (if any).
        /// </summary>
        public ChildList<ParameterDecl> Parameters
        {
            get { return _parameters; }
        }

        /// <summary>
        /// True if the anonymous method has any parameters.
        /// </summary>
        public bool HasParameters
        {
            get { return (_parameters != null && _parameters.Count > 0); }
        }

        /// <summary>
        /// The number of parameters the anonymous method has.
        /// </summary>
        public int ParameterCount
        {
            get { return (_parameters != null ? _parameters.Count : 0); }
        }

        /// <summary>
        /// Always <c>true</c>.
        /// </summary>
        public bool HasHeader
        {
            get { return true; }
        }

        /// <summary>
        /// Always <c>false</c>.
        /// </summary>
        public bool IsTopLevel
        {
            get { return false; }
        }

        #endregion

        #region /* METHODS */

        /// <summary>
        /// Create a body if one doesn't exist yet.
        /// </summary>
        public Block CreateBody()
        {
            if (_body == null)
                Body = new Block();
            return _body;
        }

        /// <summary>
        /// Create the list of <see cref="ParameterDecl"/>s, or return the existing one.
        /// </summary>
        public ChildList<ParameterDecl> CreateParameters()
        {
            if (_parameters == null)
                _parameters = new ChildList<ParameterDecl>(this);
            return _parameters;
        }

        /// <summary>
        /// Add one or more <see cref="ParameterDecl"/>s.
        /// </summary>
        public void AddParameters(params ParameterDecl[] parameterDecls)
        {
            CreateParameters().AddRange(parameterDecls);
        }

        /// <summary>
        /// Add a <see cref="CodeObject"/> to the <see cref="Block"/> body.
        /// </summary>
        public void Add(CodeObject obj)
        {
            CreateBody().Add(obj);
        }

        /// <summary>
        /// Add multiple <see cref="CodeObject"/>s to the <see cref="Block"/> body.
        /// </summary>
        public void Add(params CodeObject[] objects)
        {
            CreateBody();
            foreach (CodeObject obj in objects)
                _body.Add(obj);
        }

        /// <summary>
        /// Add a code object to the block statement, and also assign the specified comment to the object.
        /// </summary>
        /// <param name="obj">The object to be added.</param>
        /// <param name="comment">The comment text.</param>
        public void Add(CodeObject obj, string comment)
        {
            obj.AttachAnnotation(new Comment(comment));
            Add(obj);
        }

        /// <summary>
        /// Insert a <see cref="CodeObject"/> at the specified index.
        /// </summary>
        /// <param name="index">The index at which to insert.</param>
        /// <param name="obj">The object to be inserted.</param>
        public void Insert(int index, CodeObject obj)
        {
            CreateBody().Insert(index, obj);
        }

        /// <summary>
        /// Remove the specified <see cref="CodeObject"/> from the body.
        /// </summary>
        public void Remove(CodeObject obj)
        {
            if (_body != null)
                _body.Remove(obj);
        }

        /// <summary>
        /// Remove all objects from the body.
        /// </summary>
        public void RemoveAll()
        {
            if (_body != null)
                _body.RemoveAll();
        }

        /// <summary>
        /// Determine the return type of the anonymous method (never null - will be 'void' instead).
        /// </summary>
        public TypeRefBase GetReturnType()
        {
            TypeRefBase returnTypeRef = null;
            ScanReturnTypes(ref returnTypeRef, _body);
            return (returnTypeRef ?? TypeRef.VoidRef);
        }

        protected void ScanReturnTypes(ref TypeRefBase returnTypeRef, Block body)
        {
            // Recursively scan all bodies for Return statements
            if (body != null)
            {
                foreach (CodeObject codeObject in body)
                {
                    if (codeObject is Return)
                    {
                        Expression expression = ((Return)codeObject).Expression;
                        TypeRefBase typeRef = (expression == null ? TypeRef.VoidRef : expression.SkipPrefixes() as TypeRefBase);
                        returnTypeRef = (returnTypeRef == null ? typeRef : TypeRef.GetCommonType(returnTypeRef, typeRef));
                    }
                    else if (codeObject is BlockStatement)
                        ScanReturnTypes(ref returnTypeRef, ((BlockStatement)codeObject).Body);
                }
            }
        }

        /// <summary>
        /// Get the parent delegate expression.
        /// </summary>
        public virtual Expression GetParentDelegateExpression()
        {
            // Determine the delegate type to which the anonymous method is being assigned
            Expression delegateExpression = null;
            CodeObject parent = _parent;
            while (parent is Conditional || parent is IfNullThen)
                parent = parent.Parent;
            if (parent is VariableDecl)
            {
                // If the anonymous method is being used to initialize a variable, get the delegate type from the variable's type
                delegateExpression = ((VariableDecl)parent).Type;
            }
            else if (parent is Assignment)
            {
                // If the anonymous method is being assigned to a variable, get the delegate type from the variable's type
                delegateExpression = ((Assignment)parent).Left.SkipPrefixes();
            }
            else if (parent is Cast)
            {
                // If the anonymous method is being cast, get the delegate type from the cast
                delegateExpression = ((Cast)parent).Type;
            }
            else if (parent is Return)
            {
                // If the anonymous method is being returned from a method, get the delegate type from the method return type
                CodeObject parentMethod = parent.FindParentMethod();
                if (parentMethod is MethodDeclBase)
                    delegateExpression = ((MethodDeclBase)parentMethod).ReturnType;
                else if (parentMethod is AnonymousMethod)
                {
                    // If the anonymous method is being returned from another anonymous method, recursively get the parent
                    // delegate expression of the parent anonymous method, then get the return type of that delegate.
                    delegateExpression = ((AnonymousMethod)parentMethod).GetParentDelegateExpression();
                    if (delegateExpression != null)
                        delegateExpression = delegateExpression.GetDelegateReturnType();
                }
            }
            else if (parent is YieldReturn)
            {
                // If the anonymous method is being returned from an iterator method using 'yield return', get the delegate
                // type from the method return type, minus the IEnumerable<> wrapper.
                CodeObject parentMethod = parent.FindParentMethod();
                if (parentMethod is MethodDeclBase)
                {
                    delegateExpression = ((MethodDeclBase)parentMethod).ReturnType;
                    TypeRefBase typeRefBase = delegateExpression.SkipPrefixes() as TypeRefBase;
                    if (typeRefBase != null && typeRefBase.IsSameGenericType(TypeRef.IEnumerable1Ref))
                        delegateExpression = typeRefBase.TypeArguments[0];
                }
            }
            else if (parent is Initializer)
            {
                // Handle collection initializers
                if (parent.Parent is Initializer && parent.Parent.Parent is NewObject)
                {
                    TypeRefBase typeRefBase = ((NewObject)parent.Parent.Parent).SkipPrefixes() as TypeRefBase;

                    // Handle a Dictionary where the value is a delegate type
                    if (typeRefBase != null && typeRefBase.IsSameGenericType(TypeRef.Dictionary2Ref))
                        delegateExpression = typeRefBase.TypeArguments[1];
                }
            }
            return delegateExpression;
        }

        /// <summary>
        /// Deep-clone the code object.
        /// </summary>
        public override CodeObject Clone()
        {
            AnonymousMethod clone = (AnonymousMethod)base.Clone();
            clone._parameters = ChildListHelpers.Clone(_parameters, clone);
            clone.CloneField(ref clone._body, _body);
            return clone;
        }

        #endregion

        #region /* PARSING */

        /// <summary>
        /// The token used to parse the code object.
        /// </summary>
        public const string ParseToken = DelegateDecl.ParseToken;

        /// <summary>
        /// The token used to parse the start of the parameter list of an anonymous method.
        /// </summary>
        public const string ParseTokenStart = ParameterDecl.ParseTokenStart;

        /// <summary>
        /// The token used to parse the end of the parameter list of an anonymous method.
        /// </summary>
        public const string ParseTokenEnd = ParameterDecl.ParseTokenEnd;

        internal static new void AddParsePoints()
        {
            // Use a parse-priority of 100 (DelegateDecl uses 0)
            Parser.AddParsePoint(ParseToken, 100, Parse);
        }

        /// <summary>
        /// Parse an <see cref="AnonymousMethod"/>.
        /// </summary>
        public static AnonymousMethod Parse(Parser parser, CodeObject parent, ParseFlags flags)
        {
            return new AnonymousMethod(parser, parent);
        }

        protected AnonymousMethod(Parser parser, CodeObject parent)
            : base(parser, parent)
        {
            Token startingToken = parser.Token;  // Get the object starting token
            parser.NextToken();                  // Move past 'delegate'

            // Parse the parameter declarations
            bool isEndFirstOnLine;
            _parameters = ParameterDecl.ParseList(parser, this, ParseTokenStart, ParseTokenEnd, true, out isEndFirstOnLine);
            IsEndFirstOnLine = isEndFirstOnLine;

            // If the body is indented less than the parent object, set the NoIndentation flag to prevent it from
            // being formatted relative to the parent object.
            if (parser.CurrentTokenIndentedLessThan(startingToken))
                SetFormatFlag(FormatFlags.NoIndentation, true);

            new Block(out _body, parser, this, true);  // Parse the body
        }

        #endregion

        #region /* FORMATTING */

        /// <summary>
        /// True if the anonymous method has braces by default.
        /// </summary>
        public virtual bool HasBracesDefault
        {
            get { return true; }
        }

        /// <summary>
        /// True if the anonymous method is delimited by braces.
        /// </summary>
        public bool HasBraces
        {
            get { return (_body != null && _body.HasBraces); }
            set
            {
                CreateBody().HasBraces = value;

                // If we just turned on braces, force a terminator on any expression in the body
                if (value && _body.Count > 0)
                    _body[0].HasTerminator = true;
            }
        }

        /// <summary>
        /// Reformat the <see cref="Block"/> body.
        /// </summary>
        public virtual void ReformatBlock()
        {
            if (_body != null)
            {
                if (!_body.IsGroupingSet)
                    _body.SetFormatFlag(FormatFlags.Grouping, HasBracesDefault);
                IsSingleLine = (IsSingleLineDefault && _body.IsSingleLineDefault && _body.Count < 2);
                if (_body.Count == 0)
                    _body.SetNewLines(0);
            }
        }

        /// <summary>
        /// Determines if the code object only requires a single line for display.
        /// </summary>
        public override bool IsSingleLine
        {
            get { return (base.IsSingleLine && (_body == null || (!_body.IsFirstOnLine && _body.IsSingleLine))); }
            set
            {
                // Make sure there's a body, and set its IsFirstOnLine and IsSingleLine properties appropriately
                CreateBody();
                _body.IsFirstOnLine = !value;
                _body.IsSingleLine = value;
            }
        }

        #endregion

        #region /* RENDERING */

        protected override void AsTextAfter(CodeWriter writer, RenderFlags flags)
        {
            if (!flags.HasFlag(RenderFlags.Description))
            {
                base.AsTextAfter(writer, flags);
                if (_body != null)
                {
                    // Increase the indent level for the body (unless disabled)
                    if (!HasNoIndentation)
                        writer.BeginIndentOnNewLineRelativeToParentOffset(this, true);
                    _body.AsText(writer, flags);
                    if (!HasNoIndentation)
                        writer.EndIndentation(this);
                }
            }
        }

        public override void AsText(CodeWriter writer, RenderFlags flags)
        {
            // This method is overridden so that any parens around the anonymous method will be rendered with the close
            // paren after the body (after AsTextAfter).  While we're at it, prefix support is omitted.
            int newLines = NewLines;
            if (newLines > 0)
            {
                if (!flags.HasFlag(RenderFlags.SuppressNewLine))
                    writer.WriteLines(newLines);
            }
            else if (flags.HasFlag(RenderFlags.PrefixSpace))
                writer.Write(" ");

            RenderFlags passFlags = (flags & RenderFlags.PassMask);
            AsTextBefore(writer, passFlags | RenderFlags.IsPrefix);
            UpdateLineCol(writer, flags);

            // Increase the indent level for any newlines that occur within the expression
            bool increaseIndent = flags.HasFlag(RenderFlags.IncreaseIndent);
            if (increaseIndent)
                writer.BeginIndentOnNewLine(this);

            bool hasParens = HasParens;
            if (hasParens)
                writer.Write(ParseTokenStartGroup);
            AsTextExpression(writer, passFlags | (flags & (RenderFlags.Attribute | RenderFlags.HasDotPrefix | RenderFlags.Declaration)));
            if (HasTerminator && !flags.HasFlag(RenderFlags.Description))
            {
                writer.Write(Statement.ParseTokenTerminator);
                CheckForAlignment(writer);  // Check for alignment of any EOL comments
            }
            if (!flags.HasFlag(RenderFlags.NoEOLComments))
                AsTextEOLComments(writer, flags);

            AsTextAfter(writer, passFlags | (flags & RenderFlags.NoPostAnnotations));
            if (hasParens)
            {
                if (IsEndFirstOnLine)
                    writer.WriteLine();
                writer.Write(ParseTokenEndGroup);
            }

            if (increaseIndent)
                writer.EndIndentation(this);
        }

        public override void AsTextExpression(CodeWriter writer, RenderFlags flags)
        {
            RenderFlags passFlags = (flags & RenderFlags.PassMask);
            if (!flags.HasFlag(RenderFlags.Description))
            {
                writer.SetParentOffset();
                UpdateLineCol(writer, flags);
                writer.Write(ParseToken);
                bool showParens = (_parameters != null || HasInfixComments);
                if (showParens)
                    writer.Write(ParseTokenStart);
                AsTextInfixComments(writer, 0, flags);
                writer.WriteList(_parameters, passFlags, this);
                if (showParens)
                    writer.Write(ParseTokenEnd);
            }
            else
            {
                // If rendering as a description, just render as a delegate type
                GetReturnType().AsText(writer, passFlags | RenderFlags.IsPrefix);
                writer.Write(ParseToken);
                writer.Write(ParseTokenStart);
                writer.WriteList(_parameters, passFlags, this);
                if (IsEndFirstOnLine)
                    writer.WriteLine();
                writer.Write(ParseTokenEnd);
            }
        }

        #endregion
    }
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The Common Development and Distribution License (CDDL)


Written By
Software Developer (Senior)
United States United States
I've been writing software since the late 70's, currently focusing mainly on C#.NET. I also like to travel around the world, and I own a Chocolate Factory (sadly, none of my employees are oompa loompas).

Comments and Discussions