Click here to Skip to main content
15,886,787 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.Generic;

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

namespace Nova.CodeDOM
{
    /// <summary>
    /// The common base class of all <see cref="Statement"/>s that can have a <see cref="Block"/> as a body (<see cref="NamespaceDecl"/>,
    /// <see cref="TypeDecl"/>, <see cref="MethodDeclBase"/>, <see cref="PropertyDeclBase"/>, <see cref="BlockDecl"/>, <see cref="IfBase"/>,
    /// <see cref="Else"/>, <see cref="Switch"/>, <see cref="SwitchItem"/>, <see cref="For"/>, <see cref="ForEach"/>, <see cref="While"/>,
    /// <see cref="Try"/>, <see cref="Catch"/>, <see cref="Finally"/>, <see cref="Using"/>, <see cref="Lock"/>, <see cref="CheckedBlock"/>,
    /// <see cref="UncheckedBlock"/>).
    /// </summary>
    public abstract class BlockStatement : Statement, IBlock
    {
        #region /* FIELDS */

        /// <summary>
        /// The body is always a Block, which in turn may contain zero or more other code objects,
        /// and it can also be null in special cases (such as for method signatures with no body,
        /// delegate declarations, or a While with the semi-colon on the same line).
        /// </summary>
        protected Block _body;

        #endregion

        #region /* CONSTRUCTORS */

        /// <summary>
        /// Create a <see cref="BlockStatement"/>.
        /// </summary>
        protected BlockStatement()
        {
            Body = new Block();
        }

        /// <summary>
        /// Create a <see cref="BlockStatement"/> with the specified <see cref="CodeObject"/> in the body.
        /// </summary>
        protected BlockStatement(CodeObject body, bool allowNullBody)
        {
            // Allow derived classes to pass any non-Block code object, in which case it will
            // be wrapped in a Block.
            Body = (body == null ? (allowNullBody ? null : new Block()) : (body is Block ? (Block)body : new Block(body)));
        }

        /// <summary>
        /// Create a <see cref="BlockStatement"/> from an array of <see cref="CodeObject"/>s.
        /// </summary>
        protected BlockStatement(params CodeObject[] objects)
        {
            Add(objects);
        }

        /// <summary>
        /// Create a <see cref="BlockStatement"/> from an existing one, moving the body.
        /// </summary>
        protected BlockStatement(BlockStatement blockStatement)
            : base(blockStatement)
        {
            _body = blockStatement.Body;  // bypass body formatting
            blockStatement.Body = null;
            _body.Parent = this;
        }

        #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();
                }
                HasTerminator = HasTerminatorDefault;
            }
        }

        /// <summary>
        /// True for all <see cref="BlockStatement"/>s that have a header (all except <see cref="CodeUnit"/> and <see cref="BlockDecl"/>).
        /// </summary>
        public virtual bool HasHeader
        {
            get { return true; }
        }

        /// <summary>
        /// True if a <see cref="BlockStatement"/> is at the top level (those that have no header and no indent).
        /// For example, a <see cref="CodeUnit"/>, a <see cref="BlockDecl"/> with no parent or a <see cref="DocComment"/>
        /// or <see cref="Project"/> parent.
        /// </summary>
        public virtual bool IsTopLevel
        {
            get { return (!HasHeader && (_parent == null || _parent is DocComment || _parent is Project)); }
        }

        /// <summary>
        /// True for multi-part statements, such as try/catch/finally or if/else.
        /// </summary>
        public virtual bool IsMultiPart
        {
            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>
        /// Add a <see cref="CodeObject"/> to the <see cref="BlockStatement"/> body.
        /// </summary>
        public virtual void Add(CodeObject obj)
        {
            CreateBody().Add(obj);
        }

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

        /// <summary>
        /// Add a collection of <see cref="CodeObject"/>s to the <see cref="BlockStatement"/> body.
        /// </summary>
        /// <param name="collection">The collection to be added.</param>
        public virtual void AddRange(IEnumerable<CodeObject> collection)
        {
            CreateBody().AddRange(collection);
        }

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

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

        /// <summary>
        /// Remove the <see cref="CodeObject"/> at the specified index from the <see cref="BlockStatement"/>.
        /// </summary>
        public void RemoveAt(int index)
        {
            if (_body != null)
                _body.RemoveAt(index);
        }

        /// <summary>
        /// Remove all <see cref="CodeObject"/>s from the <see cref="BlockStatement"/> body.
        /// </summary>
        public virtual void RemoveAll()
        {
            if (_body != null)
                _body.RemoveAll();
        }

        /// <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)
        {
            return (_body != null && _body.Replace(oldObject, newObject));
        }

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

        /// <summary>
        /// Check if the <see cref="BlockStatement"/> 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 (_body != null && _body.Contains(codeObject));
        }

        /// <summary>
        /// Enumerate all children with the specified name.
        /// </summary>
        public IEnumerable<CodeObject> Find(string name)
        {
            if (_body != null)
            {
                foreach (CodeObject codeObject in _body.Find(name))
                    yield return codeObject;
            }
        }

        /// <summary>
        /// Enumerate all children with the specified name and type.
        /// </summary>
        public IEnumerable<T> Find<T>(string name) where T : CodeObject
        {
            if (_body != null)
            {
                foreach (T codeObject in _body.Find<T>(name))
                    yield return codeObject;
            }
        }

        /// <summary>
        /// Enumerate all children of type T.
        /// </summary>
        public IEnumerable<T> Find<T>() where T : CodeObject
        {
            if (_body != null)
            {
                foreach (T codeObject in _body.Find<T>())
                    yield return codeObject;
            }
        }

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

        /// <summary>
        /// Find the first child object of type T.
        /// </summary>
        public T FindFirst<T>() where T : CodeObject
        {
            return (_body != null ? _body.FindFirst<T>() : null);
        }

        /// <summary>
        /// Find the index of the specified <see cref="CodeObject"/> in the <see cref="BlockStatement"/>.
        /// </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)
        {
            return (_body != null ? _body.FindIndexOf(codeObject) : -1);
        }

        #endregion

        #region /* PARSING */

        protected BlockStatement(Parser parser, CodeObject parent)
            : base(parser, parent)
        { }

        protected void ParseKeywordArgumentBody(Parser parser, ref Expression argument, bool allowNullBody, bool noPostProcessing)
        {
            ParseKeywordAndArgument(parser, ref argument);  // Parse the keyword and argument

            if (allowNullBody && parser.TokenText == Terminator && !parser.Token.IsFirstOnLine)
                ParseTerminator(parser);  // Handle same-line ';' (null body)
            else
                new Block(out _body, parser, this, false);  // Parse the body
        }

        protected void ParseTerminatorOrBody(Parser parser, ParseFlags flags)
        {
            // Check for an optional ';' in place of the body
            if (parser.TokenText == Terminator)
            {
                ParseTerminator(parser);

                // Check for compiler directives, storing them as postfix annotations on the parent
                Block.ParseCompilerDirectives(parser, this, AnnotationFlags.IsPostfix, false);
            }
            else
            {
                if (flags.HasFlag(ParseFlags.SkipMethodBodies))
                    Block.SkipParsingBlock(parser, this, true);
                else
                    new Block(out _body, parser, this, true);  // Parse the body
            }
        }

        #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)
        {
            if (_body != null)
                _body.Resolve(ResolveCategory.CodeObject, flags);
            return this;
        }

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

        /// <summary>
        /// Resolve indexers.
        /// </summary>
        public virtual void ResolveIndexerRef(Resolver resolver)
        {
            if (_body != null)
                _body.ResolveIndexerRef(resolver);
        }

        /// <summary>
        /// Resolve child code objects that match the specified name, moving up the tree until a complete match is found.
        /// </summary>
        public override void ResolveRefUp(string name, Resolver resolver)
        {
            if (_body != null)
            {
                _body.ResolveRef(name, resolver);
                if (resolver.HasCompleteMatch) return;  // Abort if we found a match
            }
            if (_parent != null)
                _parent.ResolveRefUp(name, resolver);
        }

        /// <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 (_body != null)
            {
                _body.ResolveGotoTargetUp(name, resolver);
                if (resolver.HasCompleteMatch) return;  // Abort if we found a match
            }
            if (_parent != null)
                _parent.ResolveGotoTargetUp(name, resolver);
        }

        /// <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 (_body != null && _body.HasUnresolvedRef())
                return true;
            return base.HasUnresolvedRef();
        }

        #endregion

        #region /* FORMATTING */

        /// <summary>
        /// True if the <see cref="Statement"/> has an argument.
        /// </summary>
        public override bool HasArgument
        {
            get { return false; }
        }

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

        /// <summary>
        /// True if the code object only requires a single line for display by default.
        /// </summary>
        public override bool IsSingleLineDefault
        {
            get { return false; }
        }

        /// <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;
            }
        }

        /// <summary>
        /// True if the <see cref="BlockStatement"/> always requires braces.
        /// </summary>
        public virtual bool HasBracesAlways
        {
            get { return true; }
        }

        /// <summary>
        /// Determines if the body of the <see cref="BlockStatement"/> should be formatted with braces.
        /// </summary>
        public virtual bool ShouldHaveBraces()
        {
            // Check if braces aren't optional for the statement
            if (HasBracesAlways)
                return true;
            // No braces are required if we have no body, or it's empty
            if (_body == null || _body.Count == 0)
                return false;
            // Braces are required (by default) if we have multiple objects in the block
            // (this behavior is overridden by SwitchItem, where multiple objects are legal without braces).
            if (_body.Count > 1)
                return true;
            // We only have a single child statement - use braces if it's not single-line
            return !_body[0].IsSingleLine;
        }

        /// <summary>
        /// True if the <see cref="BlockStatement"/> has braces.
        /// </summary>
        public bool HasBraces
        {
            get { return (_body != null && _body.HasBraces); }
            set
            {
                if (HasBracesAlways && !value)
                    throw new Exception("Braces can't be turned off for the given type of block statement!");
                CreateBody().HasBraces = value;
            }
        }

        /// <summary>
        /// True if the <see cref="BlockStatement"/> has compact empty braces by default.
        /// </summary>
        public virtual bool IsCompactIfEmptyDefault
        {
            get { return false; }
        }

        /// <summary>
        /// True if the <see cref="BlockStatement"/> requires an empty statement if it has an empty block with no braces.
        /// </summary>
        public virtual bool RequiresEmptyStatement
        {
            get { return true; }
        }

        /// <summary>
        /// True if the <see cref="Statement"/> has a terminator character by default.
        /// </summary>
        public override bool HasTerminatorDefault
        {
            get { return (_body == null); }
        }

        /// <summary>
        /// Default format the code object.
        /// </summary>
        protected internal override void DefaultFormat()
        {
            base.DefaultFormat();

            // Default the braces if they haven't been explicitly set
            if (!IsGroupingSet)
                _formatFlags = ((_formatFlags & ~FormatFlags.Grouping) | (ShouldHaveBraces() ? FormatFlags.Grouping : 0));
        }

        #endregion

        #region /* RENDERING */

        protected override void AsTextAfter(CodeWriter writer, RenderFlags flags)
        {
            base.AsTextAfter(writer, flags);
            if (_body != null && !flags.HasFlag(RenderFlags.Description))
                _body.AsText(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