Click here to Skip to main content
15,885,920 members
Articles / Programming Languages / C#

Resolving Symbolic References in a CodeDOM (Part 7)

Rate me:
Please Sign up or sign in to vote.
4.75/5 (6 votes)
2 Dec 2012CDDL12 min read 19.4K   509   14  
Resolving symbolic references in a CodeDOM.
// 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 System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;

using Nova.Parsing;
using Nova.Rendering;
using Nova.Resolving;
using Nova.Utilities;

namespace Nova.CodeDOM
{
    /// <summary>
    /// Represents the body of a <see cref="BlockStatement"/> or <see cref="AnonymousMethod"/> (containing
    /// a sequence of 0 or more <see cref="CodeObject"/>s).
    /// </summary>
    /// <remarks>
    /// Blocks are special in that their children's Parent references link directly to the parent of the Block, in order to simplify various logic.
    /// </remarks>
    public class Block : CodeObject, ICollection<CodeObject>, ICollection
    {
        #region /* FIELDS */

        /// <summary>
        /// Child <see cref="CodeObject"/>s - the Parent of this collection will be the Block's Parent, so
        /// that the Parent of all child objects will be the Block's Parent, not the Block.
        /// </summary>
        protected ChildList<CodeObject> _codeObjects;

        /// <summary>
        /// Dictionary of named members in the block (LocalDecls, Labels, SwitchItems, or
        /// various other members if the block's parent is a TypeDecl).
        /// </summary>
        protected NamedCodeObjectDictionary _namedMembers;

        #endregion

        #region /* CONSTRUCTORS */

        /// <summary>
        /// Create a <see cref="Block"/>, optionally with the specified code objects.
        /// </summary>
        public Block(params CodeObject[] codeObjects)
        {
            // The parent will be null at this point, but when it gets set later, all child
            // objects will get their parent set correctly, and we'll also call ReformatBlock().
            _codeObjects = new ChildList<CodeObject>();
            foreach (CodeObject codeObject in codeObjects)
                AddInternal(codeObject);
        }

        #endregion

        #region /* PROPERTIES */

        /// <summary>
        /// The number of code objects in the <see cref="Block"/>.
        /// </summary>
        public int Count
        {
            get { return _codeObjects.Count; }
        }

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

        /// <summary>
        /// True if access to the <see cref="ICollection"/> is synchronized.
        /// </summary>
        public virtual bool IsSynchronized
        {
            get { return false; }
        }

        /// <summary>
        /// Gets an object that can be used to synchronize access to the <see cref="ICollection"/>.
        /// </summary>
        public virtual object SyncRoot
        {
            get { return this; }
        }

        /// <summary>
        /// Get the child <see cref="CodeObject"/> at the specified index.
        /// </summary>
        public CodeObject this[int index]
        {
            get { return _codeObjects[index]; }
        }

        /// <summary>
        /// Get the last <see cref="CodeObject"/> in the <see cref="Block"/>.
        /// </summary>
        public CodeObject Last
        {
            get { return _codeObjects.Last; }
        }

        /// <summary>
        /// The parent <see cref="CodeObject"/>.
        /// </summary>
        public override CodeObject Parent
        {
            set
            {
                // If the parent is being set for the first time, force a reformat (we can't
                // do this in the constructor, because we don't have access to the parent yet).
                if (value is IBlock && (_parent == null || _codeObjects.Count <= 2))
                    ((IBlock)value).ReformatBlock();

                base.Parent = value;
                _codeObjects.Parent = value;
            }
        }

        /// <summary>
        /// The "Infix" End-Of-Line comment for the Initializer (if any) - appears after the open brace.
        /// </summary>
        /// <remarks>
        /// This property allows for the very convenient setting of Infix EOL comments in object initializers.
        /// Although there is support for multiple Infix EOL comments on the same object, this property doesn't
        /// support that, returning the first one that it finds, and replacing all existing ones when set.
        /// </remarks>
        public string InfixEOLComment
        {
            get
            {
                // Just return the first Infix EOL comment if there is more than one
                if (_annotations != null)
                {
                    Comment comment = (Comment)Enumerable.FirstOrDefault(_annotations, delegate(Annotation annotation) { return annotation is Comment && annotation.IsEOL && annotation.IsInfix; });
                    if (comment != null)
                        return comment.Text;
                }
                return null;
            }
            set
            {
                // Remove all existing Infix EOL comments before adding the new one
                RemoveAllAnnotationsWhere<Comment>(delegate(Comment annotation) { return annotation.IsEOL && annotation.IsInfix; });
                if (value != null)
                    AttachAnnotation(new Comment(value, CommentFlags.EOL) { IsInfix = true });
            }
        }

        #endregion

        #region /* METHODS */

        /// <summary>
        /// Add a code object to the <see cref="Block"/>.
        /// </summary>
        /// <param name="codeObject">The object to be added.</param>
        public void Add(CodeObject codeObject)
        {
            AddInternal(codeObject);
            ObjectCountChanged();
        }

        /// <summary>
        /// Add multiple code objects to the <see cref="Block"/>.
        /// </summary>
        /// <param name="codeObjects">The objects to be added.</param>
        public void Add(params CodeObject[] codeObjects)
        {
            foreach (CodeObject codeObject in codeObjects)
                Add(codeObject);
        }

        /// <summary>
        /// Add a collection of code objects to the <see cref="Block"/>.
        /// </summary>
        /// <param name="collection">The collection to be added.</param>
        public void AddRange(IEnumerable<CodeObject> collection)
        {
            foreach (CodeObject codeObject in collection)
                Add(codeObject);
        }

        protected void AddInternal(CodeObject codeObject)
        {
            if (codeObject is Block)
            {
                foreach (CodeObject obj in ((Block)codeObject)._codeObjects)
                    AddInternal(obj);
            }
            else
            {
                AddInsertFormattingCheck(_codeObjects.Count, codeObject);
                AddInternalNoFormatting(codeObject);
            }
        }

        protected void AddInsertFormattingCheck(int index, CodeObject codeObject)
        {
            // Default the # of newlines for the object if it wasn't already explicitly specified
            if (!codeObject.IsNewLinesSet)
            {
                int newLines;

                // If we already have items in the block, determine newlines based upon the previous item
                CodeObject previous = null;
                for (int i = index - 1; i >= 0 && i < _codeObjects.Count; --i)
                {
                    if (!_codeObjects[i].IsGenerated)
                    {
                        previous = _codeObjects[i];
                        break;
                    }
                }
                if (previous != null)
                    newLines = codeObject.DefaultNewLines(previous);
                else
                {
                    // If we're adding or inserting the first item, then if either brace has a newline, use one, otherwise none
                    newLines = ((IsFirstOnLine || EndNewLines > 0) ? 1 : 0);
                    // If the added object has a newline, and the closing brace doesn't, make it have one now
                    if (newLines > 0 && EndNewLines == 0)
                        EndNewLines = 1;
                }
                codeObject.SetNewLines(newLines);
            }

            // Check for stand-alone expressions
            if (codeObject is Expression)
            {
                // Turn on the terminator, and default parens to off
                codeObject.HasTerminator = true;
                if (!codeObject.IsGroupingSet && ((Expression)codeObject).HasParens)
                    codeObject.SetFormatFlag(FormatFlags.Grouping, false);
            }
        }

        protected void AddInternalNoFormatting(CodeObject codeObject)
        {
            // Add the code object to the block
            _codeObjects.Add(codeObject);

            // Add named members to the block's dictionary
            if (codeObject is INamedCodeObject)
                AddNamedMember((INamedCodeObject)codeObject);

            // If any annotations are added directly to the block, send notifications
            // (this will send special comments up to the CodeUnit and Solution levels).
            if (codeObject is Annotation && ((Annotation)codeObject).IsListed)
                NotifyListedAnnotationAdded((Annotation)codeObject);
        }

        protected void AddNamedMember(INamedCodeObject namedCodeObject)
        {
            if (_namedMembers == null)
                _namedMembers = new NamedCodeObjectDictionary();
            namedCodeObject.AddToDictionary(_namedMembers);

            // If a TypeDecl is added to a NamespaceDecl, also add it to the Namespace
            if (_parent is NamespaceDecl && namedCodeObject is TypeDecl)
            {
                Namespace @namespace = ((NamespaceDecl)_parent).Namespace;
                if (@namespace != null)
                    @namespace.Add((TypeDecl)namedCodeObject);
            }
        }

        /// <summary>
        /// Clear all members from the <see cref="Block"/>.
        /// </summary>
        public void Clear()
        {
            RemoveAll();
        }

        /// <summary>
        /// Deep-clone the code object.
        /// </summary>
        public override CodeObject Clone()
        {
            Block clone = (Block)base.Clone();
            clone._codeObjects = ChildListHelpers.Clone(_codeObjects, null);
            // Re-build the dictionary using the cloned objects
            clone.RebuildDictionary();
            return clone;
        }

        /// <summary>
        /// Check if the <see cref="Block"/> contains the specified <see cref="CodeObject"/>.
        /// </summary>
        /// <param name="codeObject">The object being searched for.</param>
        /// <returns>True if the block contains the object, otherwise false.</returns>
        public bool Contains(CodeObject codeObject)
        {
            return Enumerable.Any(_codeObjects, delegate(CodeObject child) { return child == codeObject; });
        }

        /// <summary>
        /// Copy the code objects in the block to the specified array, starting at the specified offset.
        /// </summary>
        /// <param name="codeObjects">The array to copy into.</param>
        /// <param name="index">The starting index in the array.</param>
        public void CopyTo(CodeObject[] codeObjects, int index)
        {
            CopyTo((Array)codeObjects, index);
        }

        /// <summary>
        /// Copy the code objects in the block to the specified array, starting at the specified offset.
        /// </summary>
        /// <param name="array">The array to copy into.</param>
        /// <param name="index">The starting index in the array.</param>
        public void CopyTo(Array array, int index)
        {
            if (array == null)
                throw new ArgumentNullException("array", "Null array reference");
            if (index < 0)
                throw new ArgumentOutOfRangeException("index", "Index is out of range");
            if (array.Rank > 1)
                throw new ArgumentException("Array is multi-dimensional", "array");

            foreach (CodeObject obj in _codeObjects)
                array.SetValue(obj, index++);
        }

        /// <summary>
        /// Insert a code object into the block at the specified index.
        /// </summary>
        /// <param name="index">The index at which to insert.</param>
        /// <param name="codeObject">The object to be inserted.</param>
        public void Insert(int index, CodeObject codeObject)
        {
            if (codeObject is Block)
            {
                ChildList<CodeObject> codeObjects = ((Block)codeObject)._codeObjects;
                for (int i = 0; i < codeObjects.Count; ++i)
                    Insert(i, codeObjects[i]);
            }
            else
            {
                AddInsertFormattingCheck(index, codeObject);
                InsertInternalNoFormatting(index, codeObject);
                ObjectCountChanged();
            }
        }

        protected void InsertInternalNoFormatting(int index, CodeObject codeObject)
        {
            // Insert the code object into the block
            _codeObjects.Insert(index, codeObject);

            // Add named members to the block's dictionary
            if (codeObject is INamedCodeObject)
                AddNamedMember((INamedCodeObject)codeObject);

            // If any annotations are inserted directly to the block, send notifications
            // (this will send special comments up to the CodeUnit and Solution levels).
            if (codeObject is Annotation && ((Annotation)codeObject).IsListed)
                NotifyListedAnnotationAdded((Annotation)codeObject);
        }

        /// <summary>
        /// Find children with the specified name.
        /// </summary>
        /// <returns>A <see cref="CodeObject"/>, <see cref="NamedCodeObjectGroup"/>, or null if no matches were found.</returns>
        public INamedCodeObject FindChildren(string name)
        {
            return (_namedMembers != null ? _namedMembers.Find(name) : null);
        }

        /// <summary>
        /// Find children with the specified name having type T, adding them to the specified results collection.
        /// </summary>
        public void FindChildren<T>(string name, NamedCodeObjectGroup results) where T : CodeObject
        {
            INamedCodeObject foundObj = FindChildren(name);
            if (foundObj is NamedCodeObjectGroup)
            {
                foreach (INamedCodeObject namedCodeObject in (NamedCodeObjectGroup)foundObj)
                {
                    if (namedCodeObject is T)
                        results.Add(namedCodeObject);
                }
            }
            else if (foundObj is T)
                results.Add(foundObj);
        }

        /// <summary>
        /// Enumerate all children with the specified name.
        /// </summary>
        public IEnumerable<CodeObject> Find(string name)
        {
            INamedCodeObject foundObj = FindChildren(name);
            if (foundObj is NamedCodeObjectGroup)
            {
                foreach (INamedCodeObject namedCodeObject in (NamedCodeObjectGroup)foundObj)
                    yield return (CodeObject)namedCodeObject;
            }
            else
                yield return (CodeObject)foundObj;
        }

        /// <summary>
        /// Enumerate all children with the specified name and type.
        /// </summary>
        public IEnumerable<T> Find<T>(string name) where T : CodeObject
        {
            INamedCodeObject foundObj = FindChildren(name);
            if (foundObj is NamedCodeObjectGroup)
            {
                foreach (INamedCodeObject namedCodeObject in (NamedCodeObjectGroup)foundObj)
                {
                    if (namedCodeObject is T)
                        yield return (T)namedCodeObject;
                }
            }
            else if (foundObj is T)
                yield return (T)foundObj;
        }

        /// <summary>
        /// Enumerate all children of type T.
        /// </summary>
        public IEnumerable<T> Find<T>() where T : CodeObject
        {
            return Enumerable.OfType<T>(_codeObjects);
        }

        /// <summary>
        /// Find the first child object with the specified name and type.
        /// </summary>
        public T FindFirst<T>(string name) where T : CodeObject
        {
            return Enumerable.FirstOrDefault(Find<T>(name));
        }

        /// <summary>
        /// Find the first child object of type T.
        /// </summary>
        public T FindFirst<T>() where T : CodeObject
        {
            return Enumerable.FirstOrDefault(Enumerable.OfType<T>(_codeObjects));
        }

        /// <summary>
        /// Find the index of the specified <see cref="CodeObject"/> in the <see cref="Block"/>.
        /// </summary>
        /// <param name="codeObject">The object being searched for.</param>
        /// <returns>The index of the code object, or -1 if not found.</returns>
        public int FindIndexOf(CodeObject codeObject)
        {
            for (int i = 0; i < _codeObjects.Count; ++i)
                if (_codeObjects[i] == codeObject)
                    return i;
            return -1;
        }

        /// <summary>
        /// Get an enumerator for the code objects in the <see cref="Block"/>.
        /// </summary>
        IEnumerator<CodeObject> IEnumerable<CodeObject>.GetEnumerator()
        {
            return ((IEnumerable<CodeObject>)_codeObjects).GetEnumerator();
        }

        /// <summary>
        /// Get an enumerator for the code objects in the <see cref="Block"/>.
        /// </summary>
        IEnumerator IEnumerable.GetEnumerator()
        {
            return _codeObjects.GetEnumerator();
        }

        protected void ObjectCountChanged()
        {
            if (_parent is IBlock && _codeObjects.Count <= 2)
                ((IBlock)_parent).ReformatBlock();
        }

        /// <summary>
        /// Re-build the internal dictionary of named code objects in the <see cref="Block"/>.
        /// </summary>
        public void RebuildDictionary()
        {
            _namedMembers = new NamedCodeObjectDictionary();
            if (_codeObjects != null)
            {
                foreach (CodeObject codeObject in _codeObjects)
                {
                    if (codeObject is INamedCodeObject)
                        ((INamedCodeObject)codeObject).AddToDictionary(_namedMembers);
                }
            }
        }

        /// <summary>
        /// Remove the specified <see cref="CodeObject"/> from the <see cref="Block"/>.
        /// </summary>
        /// <returns>True if the code object was found and removed, otherwise false.</returns>
        public bool Remove(CodeObject codeObject)
        {
            bool removed = _codeObjects.Remove(codeObject);
            RemoveInternal(codeObject);
            return removed;
        }

        /// <summary>
        /// Remove the <see cref="CodeObject"/> at the specified index from the <see cref="Block"/>.
        /// </summary>
        public void RemoveAt(int index)
        {
            CodeObject codeObject = this[index];
            _codeObjects.RemoveAt(index);
            RemoveInternal(codeObject);
        }

        /// <summary>
        /// Remove all code objects from the <see cref="Block"/>.
        /// </summary>
        public void RemoveAll()
        {
            foreach (CodeObject codeObject in _codeObjects)
            {
                if (codeObject is Annotation && ((Annotation)codeObject).IsListed)
                    NotifyListedAnnotationRemoved((Annotation)codeObject);
            }
            _codeObjects.Clear();
            _namedMembers.Clear();
            ObjectCountChanged();
        }

        protected void RemoveInternal(CodeObject codeObject)
        {
            if (codeObject is INamedCodeObject)
                ((INamedCodeObject)codeObject).RemoveFromDictionary(_namedMembers);
            if (codeObject is Annotation && ((Annotation)codeObject).IsListed)
                NotifyListedAnnotationRemoved((Annotation)codeObject);
            ObjectCountChanged();
        }

        /// <summary>
        /// Replace the specified <see cref="CodeObject"/> with a new one.
        /// </summary>
        /// <returns>True if the code object was found and replaced, otherwise false.</returns>
        public bool Replace(CodeObject oldObject, CodeObject newObject)
        {
            int index = FindIndexOf(oldObject);
            if (index >= 0)
            {
                RemoveAt(index);
                Insert(index, newObject);
                return true;
            }
            return false;
        }

        #endregion

        #region /* PARSING */

        /// <summary>
        /// The token used to parse the start of a <see cref="Block"/>.
        /// </summary>
        public const string ParseTokenStart = "{";

        /// <summary>
        /// The token used to parse the end of a <see cref="Block"/>.
        /// </summary>
        public const string ParseTokenEnd = "}";

        /// <summary>
        /// Parse a <see cref="Block"/>.
        /// </summary>
        public Block(out Block parentBody, Parser parser, CodeObject parent, bool bracesRequired, params string[] terminators)
            : base(parser, parent)
        {
            // Clear any newline count, and set IsFirstOnLine appropriately.  Blocks use only 0 or 1 newlines on the '{',
            // with multiple newlines possible on the '}' using the EndNewLines property.
            SetNewLines(0);
            IsFirstOnLine = ((parser.Token != null && parser.Token.IsFirstOnLine) || (parent != null && parent.HasCompilerDirectives));

            // Allocate our list of child objects
            _codeObjects = new ChildList<CodeObject>(parent);

            // Tie our parent to us immediately, so that symbol resolution will work properly
            parentBody = this;

            // Don't process comments or conditional directives if we have no parent, or the parent has no header (CodeUnit or BlockDecl)
            bool processedPostAnnotations = false;
            if (parent is IBlock && ((IBlock)parent).HasHeader)
            {
                // Process comments
                parent.MoveEOLComment(parser.LastToken);  // Associate any skipped EOL comment with the parent

                // Add any skipped comment objects to the block object itself if we have an open brace
                if (parser.TokenText == ParseTokenStart)
                    MoveComments(parser.LastToken);
                // Otherwise, add them inside the block as long as it's not empty (like an empty SwitchItem block)
                else if (!StringUtil.Contains(terminators, parser.TokenText))
                    AddTrailingComments(parser.LastToken);

                // Parse any post compiler directives between the parent statement and the block
                processedPostAnnotations = ParseCompilerDirectives(parser, parent, AnnotationFlags.IsPostfix);
            }
            else
            {
                // Add any skipped comments at the top of a CodeUnit or BlockDecl (from the dummy token)
                AddTrailingComments(parser.LastToken);
            }

            // Start a new Unused list in the parser
            parser.PushUnusedList();

            // Check if we have braces - if so, we always parse to the end brace
            bool isTopLevel = (!(_parent is IBlock) || ((IBlock)_parent).IsTopLevel);
            bool singleStatement;
            bool specialTermination;
            bool hasEmptyStatement = false;
            if (parser.TokenText == ParseTokenStart)
            {
                HasBraces = true;
                parser.NextToken();                       // Move past '{'
                MoveEOLCommentAsInfix(parser.LastToken);  // Move any EOL comment as an Infix-EOL
                parser.MoveCommentsToUnused();            // Add skipped Comment objects as unused
                singleStatement = specialTermination = false;
            }
            else
            {
                if (bracesRequired)
                {
                    // If braces are required for this block, but there aren't any, it's a parse error.
                    // Just abort now with a null Block, even though we might possibly have "eaten" some
                    // comments and/or compiler directives (this could be improved).
                    parentBody = null;
                    goto abort;  // Go pop the unused list before exiting
                }

                // If terminators is null, parse to EOF (used by CodeUnit), otherwise assume a single
                // statement block unless specific terminators are provided (such as for SwitchItems).
                singleStatement = (terminators != null && terminators.Length == 0);
                specialTermination = (terminators != null && terminators.Length > 0);

                // Special handling for a block that consists only of a ';'
                if (parser.TokenText == Statement.ParseTokenTerminator)
                {
                    hasEmptyStatement = true;

                    // Skip the token so we can check any trailing comment
                    parser.NextToken();

                    // Move any EOL comment as a normal comment in the Block (a Token can only have one, and it should be the first one)
                    List<CommentBase> comments = parser.LastToken.TrailingComments;
                    if (comments != null && comments.Count > 0)
                    {
                        CommentBase comment = comments[0];
                        if (comment.IsEOL)
                        {
                            comment.IsEOL = false;
                            comment.IsFirstOnLine = true;
                            Add(comment);
                            comments.RemoveAt(0);
                        }
                    }
                    goto abort;  // Go pop the unused list before exiting
                }

                // If we don't have braces, move any trailing non-conditional directive annoations that
                // we moved to the parent above into the body of the block instead.
                if (processedPostAnnotations && !singleStatement)
                {
                    int index = parent.Annotations.Count - 1;
                    do
                    {
                        Annotation annotation = parent.Annotations[index];
                        if (annotation is Comment || (annotation is CompilerDirective && !(annotation is ConditionalDirectiveBase)))
                        {
                            parent.Annotations.RemoveAt(index);
                            InsertInternalNoFormatting(0, annotation);
                        }
                        else
                            break;
                    }
                    while (--index >= 0);
                }
            }

            bool pendingConditionalDirectives = false;

            // Push our parent as an EOL comment normalization blocker
            parser.PushNormalizationBlocker(parent);

            // Loop while not EOF, and we're either delimited by braces or we haven't hit a terminator
            while (parser.Token != null && (HasBraces || !StringUtil.Contains(terminators, parser.TokenText)))
            {
                // Stop if we're at a '}'
                if (parser.TokenText == ParseTokenEnd && !isTopLevel)
                {
                    // Check if we were expecting it
                    if (HasBraces)
                    {
                        EndNewLines = parser.Token.NewLines;  // Set the newline count for the '}'
                        parser.NextToken();                   // Eat the '}'
                        MoveEOLComment(parser.LastToken);
                    }
                    break;
                }

                // Process the current token.
                // Check for obj being null, because it might contain a '#if' directive from the preprocessing above.
                CodeObject obj = parser.ProcessToken(parent, ParseFlags.Block);
                if (obj != null)
                {
                    // If we have a complete statement, or an expression followed by a terminator, add it to the Block
                    if (obj is Statement || (obj is Expression && parser.TokenText == Statement.ParseTokenTerminator))
                    {
                        pendingConditionalDirectives = false;

                        // Eat the terminator after an Expression used as a Statement
                        if (obj is Expression)
                        {
                            obj.HasTerminator = true;
                            parser.NextToken();
                        }

                        // Associate any skipped EOL comment - with the Block if we have a single
                        // statement on the same line, otherwise with the current statement.
                        if (singleStatement && !obj.IsFirstOnLine)
                            MoveEOLComment(parser.LastToken);
                        else
                        {
                            // Move any EOL comment to the statement.
                            // If there's an inline comment between statements in the block (on the
                            // same line), make it an EOL comment on the first statement.
                            obj.MoveEOLComment(parser.LastToken);
                        }

                        bool hasConditionalDirectives = false;
                        if (parser.HasUnused)
                        {
                            hasConditionalDirectives = (parser.LastUnusedCodeObject is ConditionalDirective);
                            FlushUnused(parser);  // Flush any remaining unused objects
                        }

                        // Add the Statement to the Block
                        AddInternalNoFormatting(obj);

                        // Flush any post unused objects (used by IfBase and for trailing comments in specially terminated
                        // blocks below, that are flushed to the parent block).  Skip for single statements so that things
                        // like region directives get pushed up to the parent block of the statement just added.
                        if (parser.PostUnused != null && parser.PostUnused.Count > 0 && !singleStatement)
                        {
                            parser.MovePostUnusedToUnused();
                            FlushUnused(parser);
                        }

                        // Stop if we only wanted a single statement
                        if (singleStatement)
                        {
                            IsFirstOnLine = obj.IsFirstOnLine;

                            // If we had unused conditional directive(s) before the statement, then we
                            // should also include any that come immediately after.
                            if (hasConditionalDirectives)
                                pendingConditionalDirectives = true;
                            else
                                break;
                        }

                        // If we're at the end of a specially terminated block (i.e. case/default block), then move
                        // any trailing comments to the Post unused list for later use, otherwise flush them now.
                        if (specialTermination && (StringUtil.Contains(terminators, parser.TokenText) || parser.TokenText == ParseTokenEnd))
                            parser.MoveCommentsToPostUnused();
                        else
                            AddTrailingComments(parser.LastToken);  // Add skipped Comment objects
                    }
                    else
                    {
                        obj.MoveEOLComment(parser.LastToken);  // Associate any skipped EOL comment

                        // Special handling for pending conditional directives
                        if (pendingConditionalDirectives)
                        {
                            if (obj is ConditionalDirectiveBase)
                            {
                                AddInternalNoFormatting(obj);
                                AddTrailingComments(parser.LastToken);  // Add skipped Comment objects
                                if (obj is EndIfDirective)
                                    break;
                                obj = null;
                            }
                            else
                                pendingConditionalDirectives = false;
                        }

                        // Save the object for later use
                        if (obj != null)
                            parser.AddUnused(obj);

                        // Move any trailing comments to the unused list
                        parser.MoveCommentsToUnused();
                    }
                }
            }

            // Pop the normalization blocker object
            parser.PopNormalizationBlocker();

            // Flush any remaining unused objects
            FlushUnused(parser);

            // Do various checks if auto-cleanup is on
            if (AutomaticFormattingCleanup && !parser.IsGenerated)
            {
                if (HasBraces)
                {
                    if (_codeObjects.Count > 0)
                    {
                        // Remove unnecessary braces
                        if (_parent is BlockStatement && !((BlockStatement)_parent).ShouldHaveBraces())
                        {
                            // Also do a special check for 'else' clauses on the same line as the closing '}', changing them to be first-on-line
                            if (EndNewLines > 0 && _parent is IfBase && parser.TokenText == Else.ParseToken && parser.Token.NewLines == 0)
                                parser.Token.NewLines = 1;
                            HasBraces = false;
                        }
                    }
                    else if (_codeObjects.Count == 0)
                    {
                        // Force empty braces onto a single line if empty and the block starts on a new line and it's allowed by default
                        if (EndNewLines > 0 && IsFirstOnLine && _parent is BlockStatement && ((BlockStatement)_parent).IsCompactIfEmptyDefault)
                            EndNewLines = 0;
                    }

                    // Remove all blank lines at the end of a block
                    if (EndNewLines > 1)
                        EndNewLines = 1;
                }
                else
                {
                    // Add missing suggested braces
                    if (_codeObjects.Count > 0 && _parent is BlockStatement && ((BlockStatement)_parent).ShouldHaveBraces())
                        HasBraces = true;
                }
            }

        abort:
            // Restore the previous Unused list in the parser
            parser.PopUnusedList();

            // Post-processing: Associate comments with lone statements that follow them.
            // Scan bottom-up, so we can safely remove comments if we move them.
            for (int i = _codeObjects.Count - 2; i >= 0; --i)
            {
                // Look for comment objects that start on a new line
                CodeObject obj0 = _codeObjects[i];
                if (obj0 is CommentBase)
                    PostProcessComment((CommentBase)obj0, i);
            }

            if (!HasBraces)
            {
                // If we didn't have braces, set the block's IsFirstOnLine based on the object in it
                if (_codeObjects.Count > 0)
                {
                    IsFirstOnLine = _codeObjects[0].IsFirstOnLine;
                    EndNewLines = 0;
                }
                // If we had no children, no braces, and no empty statement, eliminate the Block object (can occur for SwitchItems)
                else if (_codeObjects.Count == 0 && !hasEmptyStatement)
                    parentBody = null;
            }
        }

        /// <summary>
        /// Parse an Expression into the Block.
        /// </summary>
        public void ParseExpressionAsBlock(Parser parser, CodeObject parent)
        {
            HasBraces = false;
            AddInternalNoFormatting(Expression.Parse(parser, parent, true));
        }

        /// <summary>
        /// Parse any compiler directives between a statement header and body (or base type list, constructor initializer, or type constraints).
        /// Also used to parse any "open" conditional directives if 'includeAll' is false.
        /// </summary>
        public static bool ParseCompilerDirectives(Parser parser, CodeObject parent, AnnotationFlags position, bool includeAll)
        {
            // If the parent statement is followed by '#', then check if it's "sandwiched" by conditional directives
            bool processedPostAnnotations = false;
            if (parser.TokenText == CompilerDirective.ParseToken)
            {
                int openIfs = 0;
                bool hasUnusedIfFirst = false, hasUnusedElses = false;
                if (!includeAll)
                {
                    // If 'includeAll' is false, then we only want to parse any "open" conditional directives.
                    // Determine how many (if any) nested open conditional directives we have by looking both at existing
                    // annotations and also unused parser objects.  Stop if we hit a regular comment that isn't preceeded
                    // by a doc comment, or a global attribute.
                    if (parent.HasAnnotations)
                    {
                        for (int i = parent.Annotations.Count - 1; i >= 0; --i)
                        {
                            Annotation annotation = parent.Annotations[i];
                            if (annotation is EndIfDirective)
                                --openIfs;
                            else if (annotation is IfDirective)
                                ++openIfs;
                            else if ((annotation is Comment && (i == 0 || !(parent.Annotations[i - 1] is DocComment)))
                                || (annotation is Attribute && ((Attribute)annotation).IsGlobal))
                                break;
                        }
                    }
                    if (parser.HasUnused)
                    {
                        for (int i = parser.Unused.Count - 1; i >= 0; --i)
                        {
                            CodeObject unused = parser.GetUnusedCodeObject(i);
                            if (unused is EndIfDirective)
                                --openIfs;
                            else if (unused is IfDirective)
                            {
                                ++openIfs;
                                if (i == 0)
                                    hasUnusedIfFirst = true;
                            }
                            else if (unused is ConditionalDirective)
                                hasUnusedElses = true;
                            else if ((unused is Comment && (i == 0 || !(parser.GetUnusedCodeObject(i - 1) is DocComment)))
                                || (unused is Attribute && ((Attribute)unused).IsGlobal))
                                break;
                        }
                    }
                    if (openIfs <= 0)
                        return false;
                }

                // Move any compiler directives to the parent as postfix directives
                int endifCount = 0;
                CodeObject firstConditional = null;
                do
                {
                    // Special case - abort if not including all and statement is just wrapped in #if/#endif
                    if (!includeAll && hasUnusedIfFirst && !hasUnusedElses && parser.PeekNextTokenText() == EndIfDirective.ParseToken && !processedPostAnnotations)
                        return false;

                    CodeObject obj = parser.ProcessToken(parent);
                    if (obj is CompilerDirective)
                    {
                        // Move trailing directives to the parent (whether it has immediately preceeding directives or not),
                        // counting total '#endif's.
                        if (obj is ConditionalDirectiveBase)
                        {
                            if (firstConditional == null)
                                firstConditional = obj;
                            if (obj is EndIfDirective)
                                ++endifCount;
                        }
                        parent.AttachAnnotation((Annotation)obj, position);
                        parent.MoveCommentsAsPost(parser.LastToken);
                        processedPostAnnotations = true;
                    }
                }
                while (parser.TokenText == CompilerDirective.ParseToken && (includeAll || endifCount < openIfs));

                // If the first trailing conditional directive wasn't an '#if', check for preceeding directives
                if (!(firstConditional is IfDirective))
                {
                    // If the parent statement is preceeded by an '#if', '#elif', or '#else', move all preceeding
                    // conditional directives to the parent (it's "sandwiched" by them), up to the total 'endifCount'
                    // (stop after that to avoid attaching compiler directives that are unrelated to the statement).
                    if (parser.LastUnusedCodeObject is ConditionalDirective)
                        parent.ParseUnusedAnnotations(parser, parent, true, (endifCount < 1 ? 1 : endifCount));
                }
            }
            return processedPostAnnotations;
        }

        /// <summary>
        /// Parse any compiler directives between a statement header and body (or base type list, constructor initializer, or type constraints).
        /// </summary>
        public static bool ParseCompilerDirectives(Parser parser, CodeObject parent, AnnotationFlags position)
        {
            return ParseCompilerDirectives(parser, parent, position, true);
        }

        /// <summary>
        /// Post-process the specified comment object at the specified index, associating with the following object if applicable.
        /// </summary>
        protected internal void PostProcessComment(CommentBase obj, int index)
        {
            if (obj.IsFirstOnLine && index < _codeObjects.Count - 1)
            {
                // Check for following statements or expressions without a preceeding blank line, or allow a couple
                // of blank lines if the comment is a documentation comment.
                CodeObject obj1 = _codeObjects[index + 1];
                int obj1NewLines = obj1.NewLines;
                if (obj1.AssociateCommentWhenParsing(obj) && (obj1NewLines <= 1 || (obj is DocComment && obj1NewLines <= 3)))
                {
                    // Remove any blank lines between DocComments and following code
                    if (obj1NewLines > 1)
                        obj1.NewLines = 1;

                    // Check that the following statement is also on a new line
                    if (obj1.IsFirstOnLine)
                    {
                        // Check if the candidate is the last object in the block, or is followed by a blank line, or is followed by
                        // either a Comment or an object with first-on-line annotations, or is followed by a compiler directive, or if
                        // the candidate comment is a documentation comment - in these cases, associate the candidate comment with the
                        // object that follows it.
                        CodeObject obj2 = (index < _codeObjects.Count - 2 ? _codeObjects[index + 2] : null);
                        if (obj2 == null || obj2.NewLines > 1 || obj2 is CommentBase || obj2.HasFirstOnLineAnnotations || obj2 is CompilerDirective || obj is DocComment)
                        {
                            // Special exception: Also require that either the comment is preceeded by a blank line, or less than two
                            // code objects, or the preceeding object is a comment or has a blank line before it, or the 2nd preceeding
                            // object is a comment.
                            if (obj.NewLines > 1 || index < 2 || _codeObjects[index - 1] is CommentBase || _codeObjects[index - 1].NewLines > 1 || _codeObjects[index - 2] is CommentBase)
                            {
                                RemoveAt(index);
                                obj1.AttachAnnotation(obj, true);
                            }
                        }
                    }
                    else
                    {
                        // If the following statement is inline, associate the inline comment with it
                        RemoveAt(index);
                        obj1.AttachAnnotation(obj, true);
                        obj1.IsFirstOnLine = true;
                        obj.IsFirstOnLine = false;
                    }
                }
            }
        }

        /// <summary>
        /// Add any trailing Comment objects on the specified token to the Block.
        /// </summary>
        protected void AddTrailingComments(Token token)
        {
            if (token != null && token.TrailingComments != null)
            {
                foreach (CommentBase commentBase in token.TrailingComments)
                {
                    // Add the comment first (so it acquires a parent), then adjust it
                    AddInternalNoFormatting(commentBase);
                    AdjustCommentIndentation(commentBase);
                }
                token.TrailingComments = null;

                // If the block has no braces, force the Line/Col info to match the first comment (if any)
                if (!HasBraces && Count > 0)
                    SetLineCol(this[0]);
            }
        }

        /// <summary>
        /// Flush unused objects in the parser into the Block.
        /// </summary>
        protected internal void FlushUnused(Parser parser)
        {
            if (parser.HasUnused)
            {
                // Special case: If the code is embedded in a '<code>' doc comment tag (we got here from Parser.ParseCodeBlockUntil),
                // and we have an empty block so far, and only a single unused object, just flush it directly to the block.
                if (parser.InDocComment && _codeObjects.Count == 0 && parser.Unused.Count == 1)
                {
                    ParsedObject unused = parser.RemoveLastUnused();
                    CodeObject obj;
                    if (unused is Token)
                        obj = new UnresolvedRef((Token)unused);
                    else
                        obj = ((UnusedCodeObject)unused).CodeObject;
                    _codeObjects.Add(obj);
                }
                else
                {
                    // Flush unused objects
                    Unrecognized unrecognized = null;
                    foreach (ParsedObject unused in parser.Unused)
                    {
                        // Flush unused tokens and expressions as Unrecognized object expressions
                        if (unused is Token || (unused is UnusedCodeObject && ((UnusedCodeObject)unused).CodeObject is Expression))
                        {
                            // Get the unused object as an expression
                            Expression expression = (unused is Token ? new UnresolvedRef((Token)unused) : (Expression)((UnusedCodeObject)unused).CodeObject);

                            // Flush the Unrecognized object anytime we start a new line
                            if (expression.IsFirstOnLine)
                                FlushUnrecognized(parser, ref unrecognized);

                            // Add the expression to the Unrecognized object
                            if (unrecognized == null)
                                unrecognized = new Unrecognized(true, unused.InDocComment, expression);
                            else
                                unrecognized.AddRight(expression);

                            // If the unused object has trailing comments, flush them now
                            if (unused.HasTrailingComments)
                            {
                                unrecognized.MoveEOLComment(unused.AsToken());  // Associate any skipped EOL comment
                                FlushUnrecognized(parser, ref unrecognized);    // Flush the Unrecognized object
                                AddTrailingComments(unused.AsToken());          // Add skipped Comment objects
                            }
                        }
                        else
                        {
                            // Flush any Unrecognized object
                            FlushUnrecognized(parser, ref unrecognized);

                            // Flush the non-Expression code object (such as a CompilerDirective or Comment)
                            UnusedCodeObject unusedCodeObject = (UnusedCodeObject)unused;
                            CodeObject codeObject = unusedCodeObject.CodeObject;
                            AddInternalNoFormatting(codeObject);
                            if (codeObject is CommentBase)
                                AdjustCommentIndentation((CommentBase)codeObject);
                            AddTrailingComments(unusedCodeObject.LastToken);  // Add skipped Comment objects
                        }
                    }

                    // Final flush of the Unrecognized object
                    FlushUnrecognized(parser, ref unrecognized);

                    parser.Unused.Clear();
                }
            }
        }

        // Flush the Unrecognized object and clear it
        protected void FlushUnrecognized(Parser parser, ref Unrecognized unrecognized)
        {
            if (unrecognized != null)
            {
                unrecognized.HasTerminator = false;  // Force the terminator off
                unrecognized.UpdateMessage();
                _codeObjects.Add(unrecognized);
                unrecognized = null;
            }
        }

        /// <summary>
        /// Skip parsing a brace-delimited <see cref="Block"/>.
        /// </summary>
        public static void SkipParsingBlock(Parser parser, CodeObject parent, bool bracesRequired, params string[] terminators)
        {
            // Don't process comments or conditional directives if we have no parent, or the parent has no header (CodeUnit or BlockDecl)
            if (parent is IBlock && ((IBlock)parent).HasHeader)
            {
                // Process comments
                parent.MoveEOLComment(parser.LastToken);  // Associate any skipped EOL comment with the parent

                // Toss any skipped comment objects
                parser.LastToken.TrailingComments = null;

                // Parse any post compiler directives between the parent statement and the block
                ParseCompilerDirectives(parser, parent, AnnotationFlags.IsPostfix);
            }
            else
            {
                // Toss any skipped comments at the top of a CodeUnit or BlockDecl (from the dummy token)
                parser.LastToken.TrailingComments = null;
            }

            // Parse the braces and everything between them, throwing it all away
            if (parser.TokenText == ParseTokenStart)
            {
                parser.NextToken();                        // Move past '{'
                parser.LastToken.TrailingComments = null;  // Toss any comments
                int nestLevel = 0;
                while (parser.Token != null && !(parser.TokenText == ParseTokenEnd && nestLevel == 0))
                {
                    if (parser.TokenText == ParseTokenStart)
                        ++nestLevel;
                    else if (parser.TokenText == ParseTokenEnd)
                        --nestLevel;
                    if (parser.TokenType == TokenType.CompilerDirective)
                        parser.ProcessToken(parent);  // Skip & toss any compiler directives
                    else
                        parser.NextToken();
                    parser.LastToken.TrailingComments = null;  // Toss any comments
                }
                if (parser.TokenText == ParseTokenEnd)
                    parser.NextToken();  // Move past '}'
            }
        }

        #endregion

        #region /* RESOLVING */

        /// <summary>
        /// Resolve all child symbolic references, using the specified <see cref="ResolveCategory"/> and <see cref="ResolveFlags"/>.
        /// </summary>
        public override CodeObject Resolve(ResolveCategory resolveCategory, ResolveFlags flags)
        {
            // Resolve using the Expression category, because expressions are legal (such as Assignments)
            // in blocks, in addition to statements (CodeObjects).
            ChildListHelpers.Resolve(_codeObjects, ResolveCategory.Expression, flags);
            return this;
        }

        /// <summary>
        /// Resolve child code objects that match the specified name.
        /// </summary>
        public void ResolveRef(string name, Resolver resolver)
        {
            if (_namedMembers != null)
                resolver.AddMatch(_namedMembers.Find(name));
        }

        /// <summary>
        /// Resolve indexers.
        /// </summary>
        public void ResolveIndexerRef(Resolver resolver)
        {
            // Although indexers (which always display their name as 'this') usually have an internal name of 'Item',
            // the IndexerName attribute can be used to override the name.  For example, StringBuilder has an indexer
            // named 'Chars', XmlAttributeCollection has 'ItemOf', etc.  So, we have to scan for all IndexerDecls.
            // Also, we don't want to match any explicit interface implementations, so filter those out.
            foreach (CodeObject codeObject in _codeObjects)
            {
                if (codeObject is IndexerDecl && !((IndexerDecl)codeObject).IsExplicitInterfaceImplementation)
                    resolver.AddMatch(codeObject);
            }
        }

        /// <summary>
        /// Resolve child code objects that match the specified name are valid goto targets, moving up the tree until a complete match is found.
        /// </summary>
        public override void ResolveGotoTargetUp(string name, Resolver resolver)
        {
            if (_namedMembers != null)
                resolver.AddMatch(_namedMembers.FindGotoTarget(name));
        }

        /// <summary>
        /// Returns true if the code object is an <see cref="UnresolvedRef"/> or has any <see cref="UnresolvedRef"/> children.
        /// </summary>
        public override bool HasUnresolvedRef()
        {
            if (ChildListHelpers.HasUnresolvedRef(_codeObjects))
                return true;
            return false;
        }

        #endregion

        #region /* FORMATTING */

        /// <summary>
        /// True if the code object only requires a single line for display by default.
        /// </summary>
        public override bool IsSingleLineDefault
        {
            get
            {
                if (_codeObjects != null)
                {
                    int count = _codeObjects.Count;
                    if (count == 0)
                        return true;
                    if (count == 1)
                        return _codeObjects[0].IsSingleLineDefault;
                }
                return false;
            }
        }

        /// <summary>
        /// Determines if the code object only requires a single line for display.
        /// </summary>
        public override bool IsSingleLine
        {
            get { return (base.IsSingleLine && (_codeObjects == null || _codeObjects.Count == 0 || (!_codeObjects[0].IsFirstOnLine && _codeObjects.IsSingleLine))); }
            set
            {
                // For Blocks, EndNewLines indicates the number of new lines before the '}'
                EndNewLines = (value ? 0 : 1);

                // Propagate the change to all members of the Block
                if (_codeObjects != null)
                {
                    CodeObject lastObj = null;
                    foreach (CodeObject obj in _codeObjects)
                    {
                        if (value)
                            obj.NewLines = 0;
                        else if (!obj.IsFirstOnLine)
                            obj.NewLines = (lastObj != null ? obj.DefaultNewLines(lastObj) : 1);
                        lastObj = obj;
                    }
                }
            }
        }

        /// <summary>
        /// True if the code object defaults to starting on a new line.
        /// </summary>
        public override bool IsFirstOnLineDefault
        {
            // Default to a single line (unless we have first-on-line annotations) - the block will be reformatted as items are added
            get { return HasFirstOnLineAnnotations; }
        }

        /// <summary>
        /// Determines if the code object appears as the first item on a line.
        /// </summary>
        public override bool IsFirstOnLine
        {
            // Special flag for IsFirstOnLine for the '{', because the newlines storage is used for EndNewLines for the '}'
            get { return _formatFlags.HasFlag(FormatFlags.InfixNewLine); }
            set { SetFormatFlag(FormatFlags.InfixNewLine, value); }
        }

        /// <summary>
        /// The number of newlines preceeding the opening '{' (0 or 1 only - setting a higher value is ignored).
        /// </summary>
        public override int NewLines
        {
            get { return (IsFirstOnLine ? 1 : 0); }
            set { IsFirstOnLine = (value > 0); }
        }

        /// <summary>
        /// The number of newlines preceeding the closing '}' (0 to N).
        /// </summary>
        public int EndNewLines
        {
            get { return (int)(_formatFlags & FormatFlags.NewLineMask); }
            set
            {
                SetNewLines(value);
                _formatFlags |= FormatFlags.NewLinesSet;
            }
        }

        /// <summary>
        /// True if the <see cref="Block"/> is delimited by braces.
        /// </summary>
        public bool HasBraces
        {
            get { return _formatFlags.HasFlag(FormatFlags.Grouping); }
            set
            {
                // Ignore any request to turn off braces if there are multiple objects in the body
                if (value || _codeObjects.Count <= 1)
                {
                    SetFormatFlag(FormatFlags.Grouping, value);
                    _formatFlags |= FormatFlags.GroupingSet;

                    if (value)
                    {
                        // If we're turning on braces, and first-on-line, force the end brace to be first-on-line
                        if (IsFirstOnLine && EndNewLines == 0)
                            EndNewLines = 1;
                    }
                    else
                    {
                        // If we're turning off braces, also clear EndNewLines
                        EndNewLines = 0;
                    }
                }
            }
        }

        /// <summary>
        /// Determines if the code object has a terminator character.
        /// </summary>
        public override bool HasTerminator
        {
            // Blocks don't have terminators, so disable their use of this flag
            get { return false; }
            set { }  // Just ignore any set attempts
        }

        #endregion

        #region /* RENDERING */

        public override void AsText(CodeWriter writer, RenderFlags flags)
        {
            if (IsGenerated)
                return;
            if (flags.HasFlag(RenderFlags.Description))
            {
                TypeRefBase.AsTextType(writer, GetType(), RenderFlags.None);
                return;
            }

            bool isTopLevel = (!(_parent is IBlock) || ((IBlock)_parent).IsTopLevel);
            if (isTopLevel)
                flags |= RenderFlags.NoBlockIndent;
            bool updatedLineCol = false;

            // Render the open brace if appropriate
            bool useBraces = HasBraces && !isTopLevel;
            if (useBraces || HasFirstOnLineAnnotations)
            {
                if (IsFirstOnLine)
                {
                    if (!flags.HasFlag(RenderFlags.SuppressNewLine))
                        writer.WriteLine();
                }
                else
                    writer.Write(" ");

                AsTextBefore(writer, flags);
                if (useBraces)
                {
                    UpdateLineCol(writer, flags);
                    updatedLineCol = true;
                    writer.Write(ParseTokenStart);
                }
                flags &= ~RenderFlags.SuppressNewLine;
            }

            if (!flags.HasFlag(RenderFlags.NoEOLComments))
                AsTextInfixEOLComments(writer, flags);

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

            // Render the body of the block
            bool isSingleLineBody = true;
            int codeObjectCount = (_codeObjects != null ? _codeObjects.Count : 0);

            // Render an empty statement ';' if we have no children or a single comment and no braces
            if ((codeObjectCount == 0 || (codeObjectCount == 1 && _codeObjects[0] is Comment)) && !HasBraces && (!(_parent is BlockStatement) || ((BlockStatement)_parent).RequiresEmptyStatement))
            {
                if (IsFirstOnLine)
                {
                    if (!flags.HasFlag(RenderFlags.SuppressNewLine))
                        writer.WriteLine();
                }
                else
                    writer.Write(" ");
                writer.Write(Statement.ParseTokenTerminator);
                if (codeObjectCount > 0)
                    writer.Write("  ");
                flags |= RenderFlags.SuppressNewLine;
            }

            int endAlignmentAt = -1;
            RenderFlags passFlags = (flags & (RenderFlags.PassMask | RenderFlags.SuppressNewLine)) | RenderFlags.PrefixSpace;
            for (int i = 0; i < codeObjectCount; ++i)
            {
                CodeObject codeObject = _codeObjects[i];
                if (codeObject.IsGenerated)
                    continue;

                // Check for newlines
                if (codeObject.NewLines > 0)
                    isSingleLineBody = false;

                // Check for possible alignment of initializations and/or EOL comments on consecutive code objects
                if (i > endAlignmentAt && !isSingleLineBody && codeObject.HasEOLComments && codeObject.IsSingleLine)
                {
                    int j = i;
                    int maxCodeWidth = 0;
                    int maxCommentWidth = 0;
                    do
                    {
                        CodeObject current = _codeObjects[j];

                        // Ignore hidden generated objects or comments without preceeding blank lines
                        if (!current.IsGenerated && !(current is CommentBase && current.NewLines == 1))
                        {
                            // Abort if we hit a blank line, or an object that doesn't have an EOL comment or isn't single-line
                            if (j > i && (current.NewLines > 1 || !current.HasEOLComments || !current.IsSingleLine))
                                break;

                            // Determine the padding width needed for alignment
                            int length = current.AsTextLength();
                            int newMaxCodeWidth = (length > maxCodeWidth ? length : maxCodeWidth);
                            int commentLength = current.EOLComment.Length;
                            int newMaxCommentWidth = (commentLength > maxCommentWidth ? commentLength : maxCommentWidth);

                            // Abort if the padded line length would be too long (IF the current line has some padding)
                            if ((writer.IndentOffset + newMaxCodeWidth + 5 + newMaxCommentWidth > MaximumLineLength)
                                && (length < newMaxCodeWidth || commentLength < newMaxCommentWidth))
                                break;

                            maxCodeWidth = newMaxCodeWidth;
                            maxCommentWidth = newMaxCommentWidth;
                        }
                        ++j;
                    }
                    while (j < _codeObjects.Count);

                    if (--j > i)
                    {
                        writer.BeginAlignment(this, new[] { maxCodeWidth });
                        endAlignmentAt = j;
                    }
                }

                // If the block's line/col info hasn't been set (no braces), we want it to match the first child, but this requires
                // processing any pending newline first, and then telling the child to suppress it.  We can't just render the child
                // first and use its line/col info, because an Assignment's line/col is the '=' character's position, and there might
                // also be prefixed annotations.
                if (i == 0 && !updatedLineCol && flags.HasFlag(RenderFlags.UpdateLineCol))
                {
                    int newLines = NewLines;
                    bool isPrefix = passFlags.HasFlag(RenderFlags.IsPrefix);
                    if (!isPrefix && newLines > 0)
                    {
                        if (!flags.HasFlag(RenderFlags.SuppressNewLine))
                        {
                            writer.WriteLines(codeObject.NewLines);
                            passFlags |= RenderFlags.SuppressNewLine;
                        }
                    }
                    else
                    {
                        writer.Write(" ");
                        passFlags &= ~RenderFlags.PrefixSpace;
                    }

                    UpdateLineCol(writer, flags);
                    updatedLineCol = true;
                }

                // Render the code object
                codeObject.AsText(writer, passFlags | (isSingleLineBody ? 0 : RenderFlags.IncreaseIndent));
                passFlags = (passFlags & ~RenderFlags.SuppressNewLine) | RenderFlags.PrefixSpace;
                flags &= ~RenderFlags.SuppressNewLine;

                // End any pending alignment if it's time
                if (i == endAlignmentAt)
                    writer.EndAlignment(this);
            }

            // Revert the indent level
            if (increaseIndent)
                writer.EndIndentation(this);

            // Render the close brace if appropriate
            if (useBraces)
            {
                int endNewLines = EndNewLines;
                if (endNewLines > 0)
                    writer.WriteLines(endNewLines);
                else
                    writer.Write(" ");
                writer.Write(ParseTokenEnd);
            }

            // Render any EOL comments (rendered after the close brace when it's on a line by itself - special Infix
            // EOL comments may also exist and be rendered after the open brace above).
            if (!flags.HasFlag(RenderFlags.NoEOLComments))
                AsTextEOLComments(writer, RenderFlags.None);

            AsTextAfter(writer, flags);
        }

        #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