Click here to Skip to main content
15,895,746 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 30.1K   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 System;
using System.Collections;
using System.Reflection;

using Nova.Parsing;
using Nova.Rendering;

namespace Nova.CodeDOM
{
    /// <summary>
    /// The common base class of all expressions.
    /// </summary>
    /// <remarks>
    /// Expressions often occur in trees, and can include operators, indexers, method (and property) calls, which operate
    /// on variables, constants, fields, etc., resulting in a specific type and value.
    /// Expressions which are valid as statements are: Assignment (and all compound assignments), Call,
    /// Increment/Decrement, PostIncrement/Decrement, Dot, and NewObject.
    /// </remarks>
    public abstract class Expression : CodeObject
    {
        #region /* CONSTRUCTORS */

        protected Expression()
        {
            SetFormatFlag(FormatFlags.Grouping, HasParensDefault);
        }

        #endregion

        #region /* PROPERTIES */

        /// <summary>
        /// True if the expression is const.
        /// </summary>
        public virtual bool IsConst
        {
            get { return false; }
        }

        /// <summary>
        /// True if the expression evaluates to a delegate type.
        /// </summary>
        public virtual bool IsDelegateType
        {
            get
            {
                TypeRefBase typeRefBase = SkipPrefixes() as TypeRefBase;
                return (typeRefBase != null && typeRefBase.IsDelegateType);
            }
        }

        /// <summary>
        /// True if the expression evaluates to a delegate, or unresolved type or a <see cref="TypeParameterRef"/>.
        /// </summary>
        public virtual bool IsPossibleDelegateType
        {
            get { return IsDelegateType; }
        }

        #endregion

        #region /* STATIC METHODS */

        /// <summary>
        /// Implicit conversion of a <see cref="Namespace"/> to an <see cref="Expression"/> (actually, a <see cref="NamespaceRef"/>).
        /// </summary>
        /// <remarks>This allows <see cref="Namespace"/> to be passed directly to any method expecting an <see cref="Expression"/> type
        /// without having to create a reference first.</remarks>
        /// <param name="namespace">The <see cref="Namespace"/> to be converted.</param>
        /// <returns>A generated <see cref="NamespaceRef"/> to the specified <see cref="Namespace"/>.</returns>
        public static implicit operator Expression(Namespace @namespace)
        {
            return @namespace.CreateRef();
        }

        /// <summary>
        /// Implicit conversion of a <see cref="Type"/> to an <see cref="Expression"/> (actually, a <see cref="TypeRef"/>).
        /// </summary>
        /// <remarks>This allows <see cref="Type"/>s such as <c>typeof(int)</c> to be passed directly to any method
        /// expecting an <see cref="Expression"/> type without having to create a reference first.</remarks>
        /// <param name="type">The <see cref="Type"/> to be converted.</param>
        /// <returns>A generated <see cref="TypeRef"/> to the specified <see cref="Type"/>.</returns>
        public static implicit operator Expression(Type type)
        {
            if (type.IsGenericTypeDefinition)
            {
                // If the type is a generic type definition, such as 'typeof(Dictonary<,>)', then default the
                // type arguments to 'null'.  If the user needs the declared type arguments instead, they will
                // have to call TypeRef.Create() directly.
                return TypeRef.Create(type, ChildList<Expression>.CreateListOfNulls(type.GetGenericArguments().Length));
            }
            return TypeRef.Create(type);
        }

        /// <summary>
        /// Implicit conversion of a <see cref="MethodBase"/> to an <see cref="Expression"/> (actually, a <see cref="MethodRef"/> or <see cref="ConstructorRef"/>).
        /// </summary>
        /// <remarks>This allows <see cref="MethodBase"/>s (<see cref="MethodInfo"/>s or <see cref="ConstructorInfo"/>s) to be passed directly
        /// to any method expecting an <see cref="Expression"/> type without having to create a reference first.</remarks>
        /// <param name="methodBase">The <see cref="MethodBase"/> to be converted.</param>
        /// <returns>A generated <see cref="MethodRef"/> to the specified <see cref="MethodBase"/>.</returns>
        public static implicit operator Expression(MethodBase methodBase)
        {
            return MethodRef.Create(methodBase);
        }

        /// <summary>
        /// Implicit conversion of a <see cref="PropertyInfo"/> to an <see cref="Expression"/> (actually, a <see cref="PropertyRef"/>).
        /// </summary>
        /// <remarks>This allows <see cref="PropertyInfo"/>s to be passed directly to any method expecting an <see cref="Expression"/> type without
        /// having to create a reference first.</remarks>
        /// <param name="propertyInfo">The <see cref="PropertyInfo"/> to be converted.</param>
        /// <returns>A generated <see cref="PropertyRef"/> to the specified <see cref="PropertyInfo"/>.</returns>
        public static implicit operator Expression(PropertyInfo propertyInfo)
        {
            return new PropertyRef(propertyInfo);
        }

        /// <summary>
        /// Implicit conversion of a <see cref="EventInfo"/> to an <see cref="Expression"/> (actually, a <see cref="EventRef"/>).
        /// </summary>
        /// <remarks>This allows <see cref="EventInfo"/>s to be passed directly to any method expecting an <see cref="Expression"/> type without
        /// having to create a reference first.</remarks>
        /// <param name="eventInfo">The <see cref="EventInfo"/> to be converted.</param>
        /// <returns>A generated <see cref="EventRef"/> to the specified <see cref="EventInfo"/>.</returns>
        public static implicit operator Expression(EventInfo eventInfo)
        {
            return new EventRef(eventInfo);
        }

        /// <summary>
        /// Implicit conversion of a <see cref="FieldInfo"/> to an <see cref="Expression"/> (actually, a <see cref="FieldRef"/>).
        /// </summary>
        /// <remarks>This allows <see cref="FieldInfo"/>s to be passed directly to any method expecting an <see cref="Expression"/> type without
        /// having to create a reference first.</remarks>
        /// <param name="fieldInfo">The <see cref="FieldInfo"/> to be converted.</param>
        /// <returns>A generated <see cref="FieldRef"/> to the specified <see cref="FieldInfo"/>.</returns>
        public static implicit operator Expression(FieldInfo fieldInfo)
        {
            return new FieldRef(fieldInfo);
        }

        /// <summary>
        /// Implicit conversion of a <see cref="TypeParameter"/> to an <see cref="Expression"/> (actually, a <see cref="TypeParameterRef"/>).
        /// </summary>
        /// <remarks>This allows <see cref="TypeParameter"/>s to be passed directly to any method expecting an <see cref="Expression"/> type
        /// without having to create a reference first.</remarks>
        /// <param name="typeParameter">The <see cref="TypeParameter"/> to be converted.</param>
        /// <returns>A generated <see cref="TypeParameterRef"/> to the specified <see cref="TypeParameter"/>.</returns>
        public static implicit operator Expression(TypeParameter typeParameter)
        {
            return typeParameter.CreateRef();
        }

        /// <summary>
        /// Implicit conversion of a <see cref="Statement"/> to an <see cref="Expression"/>.
        /// </summary>
        /// <remarks>This allows declarations to be passed directly to any method expecting an <see cref="Expression"/>
        /// type without having to create a reference first.</remarks>
        /// <param name="statement">The <see cref="Statement"/> to be converted.</param>
        /// <returns>A generated <see cref="SymbolicRef"/> to the specified <see cref="Statement"/>.</returns>
        public static implicit operator Expression(Statement statement)
        {
            return statement.CreateRef();
        }

        /// <summary>
        /// Implicit conversion of a <c>string</c> to an <see cref="Expression"/> (actually, a <see cref="Literal"/>).
        /// </summary>
        /// <remarks>This allows strings to be passed directly to any method expecting
        /// an <see cref="Expression"/> type without having to do a <c>new Literal(value)</c>.</remarks>
        /// <param name="value">The <c>string</c> to be converted.</param>
        /// <returns>A generated <see cref="Literal"/> for the specified <c>string</c>.</returns>
        public static implicit operator Expression(string value)
        {
            return new Literal(value);
        }

        /// <summary>
        /// Implicit conversion of an <c>int</c> to an <see cref="Expression"/> (actually, a <see cref="Literal"/>).
        /// </summary>
        /// <remarks>This allows ints to be passed directly to any method expecting
        /// an <see cref="Expression"/> type without having to do a <c>new Literal(value)</c>.</remarks>
        /// <param name="value">The <c>int</c> to be converted.</param>
        /// <returns>A generated <see cref="Literal"/> for the specified <c>int</c>.</returns>
        public static implicit operator Expression(int value)
        {
            return new Literal(value);
        }

        /// <summary>
        /// Implicit conversion of a <c>uint</c> to an <see cref="Expression"/> (actually, a <see cref="Literal"/>).
        /// </summary>
        /// <remarks>This allows uints to be passed directly to any method expecting
        /// an <see cref="Expression"/> type without having to do a <c>new Literal(value)</c>.</remarks>
        /// <param name="value">The <c>uint</c> to be converted.</param>
        /// <returns>A generated <see cref="Literal"/> for the specified <c>uint</c>.</returns>
        public static implicit operator Expression(uint value)
        {
            return new Literal(value);
        }

        /// <summary>
        /// Implicit conversion of a <c>bool</c> to an <see cref="Expression"/> (actually, a <see cref="Literal"/>).
        /// </summary>
        /// <remarks>This allows bools to be passed directly to any method expecting
        /// an <see cref="Expression"/> type without having to do a <c>new Literal(value)</c>.</remarks>
        /// <param name="value">The <c>bool</c> to be converted.</param>
        /// <returns>A generated <see cref="Literal"/> for the specified <c>bool</c>.</returns>
        public static implicit operator Expression(bool value)
        {
            return new Literal(value);
        }

        /// <summary>
        /// Implicit conversion of a <c>char</c> to an <see cref="Expression"/> (actually, a <see cref="Literal"/>).
        /// </summary>
        /// <remarks>This allows chars to be passed directly to any method expecting
        /// an <see cref="Expression"/> type without having to do a <c>new Literal(value)</c>.</remarks>
        /// <param name="value">The <c>char</c> to be converted.</param>
        /// <returns>A generated <see cref="Literal"/> for the specified <c>char</c>.</returns>
        public static implicit operator Expression(char value)
        {
            return new Literal(value);
        }

        /// <summary>
        /// Implicit conversion of a <c>long</c> to an <see cref="Expression"/> (actually, a <see cref="Literal"/>).
        /// </summary>
        /// <remarks>This allows longs to be passed directly to any method expecting
        /// an <see cref="Expression"/> type without having to do a <c>new Literal(value)</c>.</remarks>
        /// <param name="value">The <c>long</c> to be converted.</param>
        /// <returns>A generated <see cref="Literal"/> for the specified <c>long</c>.</returns>
        public static implicit operator Expression(long value)
        {
            return new Literal(value);
        }

        /// <summary>
        /// Implicit conversion of a <c>ulong</c> to an <see cref="Expression"/> (actually, a <see cref="Literal"/>).
        /// </summary>
        /// <remarks>This allows ulongs to be passed directly to any method expecting
        /// an <see cref="Expression"/> type without having to do a <c>new Literal(value)</c>.</remarks>
        /// <param name="value">The <c>ulong</c> to be converted.</param>
        /// <returns>A generated <see cref="Literal"/> for the specified <c>ulong</c>.</returns>
        public static implicit operator Expression(ulong value)
        {
            return new Literal(value);
        }

        /// <summary>
        /// Implicit conversion of a <c>float</c> to an <see cref="Expression"/> (actually, a <see cref="Literal"/>).
        /// </summary>
        /// <remarks>This allows floats to be passed directly to any method expecting
        /// an <see cref="Expression"/> type without having to do a <c>new Literal(value)</c>.</remarks>
        /// <param name="value">The <c>float</c> to be converted.</param>
        /// <returns>A generated <see cref="Literal"/> for the specified <c>float</c>.</returns>
        public static implicit operator Expression(float value)
        {
            return new Literal(value);
        }

        /// <summary>
        /// Implicit conversion of a <c>double</c> to an <see cref="Expression"/> (actually, a <see cref="Literal"/>).
        /// </summary>
        /// <remarks>This allows doubles to be passed directly to any method expecting
        /// an <see cref="Expression"/> type without having to do a <c>new Literal(value)</c>.</remarks>
        /// <param name="value">The <c>double</c> to be converted.</param>
        /// <returns>A generated <see cref="Literal"/> for the specified <c>double</c>.</returns>
        public static implicit operator Expression(double value)
        {
            return new Literal(value);
        }

        /// <summary>
        /// Implicit conversion of a <c>decimal</c> to an <see cref="Expression"/> (actually, a <see cref="Literal"/>).
        /// </summary>
        /// <remarks>This allows decimals to be passed directly to any method expecting
        /// an <see cref="Expression"/> type without having to do a <c>new Literal(value)</c>.</remarks>
        /// <param name="value">The <c>decimal</c> to be converted.</param>
        /// <returns>A generated <see cref="Literal"/> for the specified <c>decimal</c>.</returns>
        public static implicit operator Expression(decimal value)
        {
            return new Literal(value);
        }

        #endregion

        #region /* METHODS */

        /// <summary>
        /// Get the expression on the right of the right-most <see cref="Lookup"/> or <see cref="Dot"/> operator (bypass any '::' and '.' prefixes).
        /// </summary>
        public virtual Expression SkipPrefixes()
        {
            return this;
        }

        /// <summary>
        /// Get the expression on the left of the left-most <see cref="Dot"/> operator.
        /// </summary>
        public virtual Expression FirstPrefix()
        {
            return this;
        }

        /// <summary>
        /// Get the delegate parameters if the expression evaluates to a delegate type.
        /// </summary>
        public virtual ICollection GetDelegateParameters()
        {
            TypeRefBase typeRefBase = SkipPrefixes() as TypeRefBase;
            return (typeRefBase != null ? typeRefBase.GetDelegateParameters() : null);
        }

        /// <summary>
        /// Get the delegate return type if the expression evaluates to a delegate type.
        /// </summary>
        public virtual TypeRefBase GetDelegateReturnType()
        {
            TypeRefBase typeRefBase = SkipPrefixes() as TypeRefBase;
            return (typeRefBase != null ? typeRefBase.GetDelegateReturnType() : null);
        }

        #endregion

        #region /* PARSING */

        /// <summary>
        /// The token used to parse the start of a group.
        /// </summary>
        public const string ParseTokenStartGroup = "(";

        /// <summary>
        /// The token used to parse the end of a group.
        /// </summary>
        public const string ParseTokenEndGroup = ")";

        /// <summary>
        /// The token used to parse between expressions in a list.
        /// </summary>
        public const string ParseTokenSeparator = ",";

        internal static void AddParsePoints()
        {
            // Use a parse-priority of 400 (ConstructorDecl uses 0, MethodDecl uses 50, LambdaExpression uses 100, Call uses 200, Cast uses 300)
            Parser.AddParsePoint(ParseTokenStartGroup, 400, ParseParenthesizedExpression);
        }

        /// <summary>
        /// Parse a parenthesized <see cref="Expression"/>.
        /// </summary>
        public static Expression ParseParenthesizedExpression(Parser parser, CodeObject parent, ParseFlags flags)
        {
            parser.SaveAndNextToken();  // Save '(' for now (remove when we find the matching close)

            // Parse the expression with our parent set to block bubble-up normalization of EOL comments.
            // This also handles proper parsing of nested Conditional expressions (resetting tracking if nested expressions have parens).
            parser.PushNormalizationBlocker(parent);
            Expression expression = Parse(parser, parent, false, ParseTokenEndGroup);
            parser.PopNormalizationBlocker();

            if (expression != null)
            {
                if (expression.ParseExpectedToken(parser, ParseTokenEndGroup))  // Move past ')'
                {
                    Token open = parser.RemoveLastUnusedToken();  // Remove '('
                    expression.MoveFormatting(open);
                    expression.HasParens = true;
                    if (parser.LastToken.IsFirstOnLine)
                        expression.IsEndFirstOnLine = true;

                    // Move any comments after the '(' to the argument expression as regular (inline if necessary) comments
                    expression.MoveCommentsToLeftMost(open, true);

                    // Associate any trailing EOL or inline comment on the ')'.
                    // Skip this for directive expressions, because there is no terminating character for
                    // directive expressions to be associated with, and they'll end up here all the time.
                    if (!parser.InDirectiveExpression)
                        expression.MoveEOLComment(parser.LastToken);
                }
            }
            return expression;
        }

        /// <summary>
        /// Parse an expression, stopping when default terminators, or the specified terminators, or a higher-precedence
        /// operator is encountered.
        /// </summary>
        /// <param name="parser">The parser object.</param>
        /// <param name="parent">The parent object.</param>
        /// <param name="isTopLevel">True if EOL comments can be associated with the expression during parsing - generally
        /// true if the parent is a statement or expression list, but with some exceptions.</param>
        /// <param name="terminator">Optional terminating characters (null if none).</param>
        /// <param name="flags">Parsing flags.</param>
        /// <returns>The parsed <see cref="Expression"/>.</returns>
        public static Expression Parse(Parser parser, CodeObject parent, bool isTopLevel, string terminator, ParseFlags flags)
        {
            // Save the starting token of the expression for later
            Token startingToken = parser.Token;

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

            // Parse an expression, which can be in one of the following formats:
            //   - An identifier token, optionally followed by an operator (which is parsed only if precedence rules determine it should be)
            //   - An operator (which will itself look for previous and/or following expressions when parsed)
            //   - An open paren, expression, close paren sequence (handled by the installed parse-point above), optionally followed by an operator
            // Any other sequence will cause parsing of the expression to cease.
            // The expression will be terminated by any of ';,}]', or other specified terminator.

            // Create a string of possible terminators (assuming 1 char terminators for now)
            string terminators = Statement.ParseTokenTerminator + ParseTokenSeparator + Block.ParseTokenEnd + Index.ParseTokenEnd + terminator;

            // Keep a reference to the last token so we can move any skipped non-EOL comments to the expression later
            Token lastToken = parser.LastToken;

            // Loop until EOF or we find a terminator, or for directive expressions stop if we find a comment or a token on a new line.
            // NOTE: Keep this logic in sync with the 'if' statement further down in the loop that checks for termination.
            while (parser.TokenText != null
                && (parser.TokenText.Length != 1 || terminators.IndexOf(parser.TokenText[0]) < 0)
                && (!parser.InDirectiveExpression || ((parser.LastToken.TrailingComments == null || parser.LastToken.TrailingComments.Count == 0) && !parser.Token.IsFirstOnLine)))
            {
            process_next:
                bool skipTerminationCheck = false;

                // Process the current token (will process operators)
                CodeObject obj = parser.ProcessToken(parent, flags | ParseFlags.Expression);
                if (obj != null)
                {
                    // If we got something, save it for later.
                    // Don't move any EOL comments here - they should have already been processed.

                    if (obj is CompilerDirective)
                    {
                        // If we have a compiler directive, and there's a preceeding unused object, add it there
                        CodeObject lastUnusedCodeObject = parser.LastUnusedCodeObject;
                        if (lastUnusedCodeObject != null && !(lastUnusedCodeObject is CompilerDirective))
                            lastUnusedCodeObject.AttachAnnotation((CompilerDirective)obj, AnnotationFlags.IsPostfix);
                        else
                        {
                            parser.AddUnused(obj);  // Add the object to the unused list
                            skipTerminationCheck = true;
                        }
                    }
                    else
                    {
                        obj.ParseUnusedAnnotations(parser, parent, true);  // Parse any annotations from the Unused list
                        parser.AddUnused(obj);                             // Add the object to the unused list
                    }
                }

                // Stop if EOF or we find a terminator, or for directive expressions stop if we find a comment or a token on a new line.
                // NOTE: Keep this logic in sync with that in the condition of the parent 'while' loop.
                if (parser.TokenText == null
                    || (parser.TokenText.Length == 1 && terminators.IndexOf(parser.TokenText[0]) >= 0)
                    || (parser.InDirectiveExpression && ((parser.LastToken.TrailingComments != null && parser.LastToken.TrailingComments.Count != 0) || parser.Token.IsFirstOnLine)))
                {
                    // Don't abort here on a '{' terminator if we're in a doc comment and we appear to have type arguments using
                    // braces (as opposed to an Initializer after a NewObject).  Go process the next object immediately instead.
                    if (parser.InDocComment && parser.TokenText == TypeRefBase.ParseTokenAltArgumentStart && parser.HasUnusedIdentifier
                        && TypeRefBase.PeekTypeArguments(parser, TypeRefBase.ParseTokenAltArgumentEnd, flags))
                        goto process_next;
                    break;
                }

                // If the current token is the start of a compiler directive, check for special situations in which we want to skip
                // the termination check below.  This allows the directive to be attached to preceeding code objects such as literals
                // or operators, while not attaching to simple name or type expressions which might be part of a namespace or type header.
                if (parser.TokenText == CompilerDirective.ParseToken)
                {
                    CodeObject lastUnusedCodeObject = parser.LastUnusedCodeObject;
                    if (lastUnusedCodeObject is Literal || lastUnusedCodeObject is Operator)
                    {
                        skipTerminationCheck = true;
                        // Also, capture any pending trailing comments
                        if (obj != null)
                            obj.MoveCommentsAsPost(parser.LastToken);
                    }
                }

                // If we don't have a specific terminating character, then we're parsing a sub-expression and we should stop when we
                // get to an invalid operator, or an operator of greater precedence.  Skip this check if we just parsed a compiler
                // directive and didn't have a preceeding code object to attach it to, or if we're about to parse a compiler directive
                // and we have an unused code object that we'd like to attach it to.
                if (terminator == null && !skipTerminationCheck)
                {
                    // Check for '{' when used inside a doc comment in a generic type constructor or generic method instance
                    if (parser.InDocComment && parser.TokenText == TypeRefBase.ParseTokenAltArgumentStart
                        && TypeRefBase.PeekTypeArguments(parser, TypeRefBase.ParseTokenAltArgumentEnd, flags))
                        continue;

                    // Check if the current token represents a valid operator
                    Parser.OperatorInfo operatorInfo = parser.GetOperatorInfoForToken();

                    // If the current token doesn't look like a valid operator, we're done with the expression
                    if (operatorInfo == null)
                        break;

                    // We have an operator - check if our parent is also an operator
                    if (parent is Operator)
                    {
                        // Special cases for Types:  Some operator symbols are overloaded and can also be part
                        // of a type name.  We must detect these here, and continue processing in these cases,
                        // skipping the operator precedence checks below that terminate the current expression.

                        // Check for '[' when used in an array type name
                        if (parser.TokenText == TypeRefBase.ParseTokenArrayStart && TypeRefBase.PeekArrayRanks(parser))
                            continue;

                        // Check for '<' when used in a generic type constructor or generic method instance
                        if (parser.TokenText == TypeRefBase.ParseTokenArgumentStart && TypeRefBase.PeekTypeArguments(parser, TypeRefBase.ParseTokenArgumentEnd, flags))
                            continue;

                        // Do NOT check for '?' used for nullable types, because it applies to the entire
                        // expression on the left, so we DO want to terminate processing.

                        // Determine the precedence of the parent operator
                        // NOTE: See the bottom of Operator.cs for a quick-reference of operator precedence.
                        int parentPrecedence = ((Operator)parent).GetPrecedence();

                        // Stop parsing if the parent operator has higher precedence
                        if (parentPrecedence < operatorInfo.Precedence)
                            break;

                        // If the parent has the same precedence, stop parsing if the operator is left-associative
                        if (parentPrecedence == operatorInfo.Precedence && operatorInfo.LeftAssociative)
                            break;
                    }
                }
            }

            // Get the expression
            Expression expression = parser.RemoveLastUnusedExpression();
            if (expression != null)
            {
                // Attach any skipped non-EOL comments from the front of the expression, but only if we're a top-level expression
                // (otherwise, comments that preceed a sub-expression will get attached to an outer expression instead).  This
                // prevents lost comments in places such as between a 'return' and the expression that follows.
                if (isTopLevel)
                    expression.MoveAllComments(lastToken);

                // If this is a top-level expression or if the next token is a close paren, move any trailing comments on the last
                // token of the expression as post comments. This prevents lost comments in places such as when some trailing parts of
                // an 'if' conditional expression are commented-out, or the trailing parts of any sub-expression before a close paren.
                if ((isTopLevel || parser.TokenText == ParseTokenEndGroup) && parser.LastToken.HasTrailingComments && !parser.InDirectiveExpression)
                    expression.MoveCommentsAsPost(parser.LastToken);
            }

            // Flush remaining unused objects as Unrecognized objects
            while (parser.HasUnused)
            {
                Expression preceedingUnused = parser.RemoveLastUnusedExpression(true);
                if (preceedingUnused != null)
                {
                    if (expression == null)
                        expression = new Unrecognized(false, parser.InDocComment, preceedingUnused);
                    else if (expression is Unrecognized && !expression.HasParens)
                        ((Unrecognized)expression).AddLeft(preceedingUnused);
                    else
                        expression = new Unrecognized(false, parser.InDocComment, preceedingUnused, expression);
                }
                else
                {
                    // If we have no expression to put them on, then parse any preceeding compiler directives into a temp object for later retrieval
                    if (expression == null)
                        expression = new TempExpr();
                    expression.ParseUnusedAnnotations(parser, parent, true);
                    break;
                }
            }
            if (expression is Unrecognized)
                ((Unrecognized)expression).UpdateMessage();

            parser.Unused.Clear();

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

            if (expression != null)
            {
                // Get any EOL comments
                if (parser.LastToken.HasTrailingComments)
                    expression.MoveEOLComment(parser.LastToken);

                // Set the parent starting token to the beginning of the expression
                parser.ParentStartingToken = startingToken;
            }

            return expression;
        }

        /// <summary>
        /// Parse an expression, stopping when default terminators, or the specified terminators, or a higher-precedence
        /// operator is encountered.
        /// </summary>
        /// <param name="parser">The parser object.</param>
        /// <param name="parent">The parent object.</param>
        /// <param name="isTopLevel">True if EOL comments can be associated with the expression during parsing - generally
        /// true if the parent is a statement or expression list, but with some exceptions.</param>
        /// <param name="terminator">Optional terminating characters (null if none).</param>
        /// <returns>The parsed <see cref="Expression"/>.</returns>
        public static Expression Parse(Parser parser, CodeObject parent, bool isTopLevel, string terminator)
        {
            return Parse(parser, parent, isTopLevel, terminator, ParseFlags.None);
        }

        /// <summary>
        /// Parse an expression, stopping when default terminators, or the specified terminators, or a higher-precedence
        /// operator is encountered.
        /// </summary>
        /// <param name="parser">The parser object.</param>
        /// <param name="parent">The parent object.</param>
        /// <param name="isTopLevel">True if EOL comments can be associated with the expression during parsing - generally
        /// true if the parent is a statement or expression list, but with some exceptions.</param>
        /// <returns>The parsed <see cref="Expression"/>.</returns>
        public static Expression Parse(Parser parser, CodeObject parent, bool isTopLevel)
        {
            return Parse(parser, parent, isTopLevel, null, ParseFlags.None);
        }

        /// <summary>
        /// Parse an expression, stopping when default terminators, or the specified terminators, or a higher-precedence
        /// operator is encountered.
        /// </summary>
        /// <param name="parser">The parser object.</param>
        /// <param name="parent">The parent object.</param>
        /// <returns>The parsed <see cref="Expression"/>.</returns>
        public static Expression Parse(Parser parser, CodeObject parent)
        {
            return Parse(parser, parent, false, null, ParseFlags.None);
        }

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

        /// <summary>
        /// Parse a directive <see cref="Expression"/>.
        /// </summary>
        public static Expression ParseDirectiveExpression(Parser parser, CodeObject parent)
        {
            parser.InDirectiveExpression = true;
            Expression expression = Parse(parser, parent, true);
            parser.InDirectiveExpression = false;
            return expression;
        }

        /// <summary>
        /// Parse a list of <see cref="Expression"/>s.
        /// </summary>
        public static ChildList<Expression> ParseList(Parser parser, CodeObject parent, string terminator, ParseFlags flags, bool allowSingleNullList)
        {
            ChildList<Expression> list = null;
            bool skipStatementTerminators = (terminator == Initializer.ParseTokenEnd);
            bool lastCommaFirstOnLine = false;
            while (true)
            {
                Expression expression = Parse(parser, parent, true, terminator, flags);
                bool hasComma = (parser.TokenText == ParseTokenSeparator);
                if (expression != null)
                {
                    // Force the expression to first-on-line if the last comma was (handles special-case
                    // formatting where the commas preceed the list items instead of following them).
                    if (lastCommaFirstOnLine)
                        expression.IsFirstOnLine = true;

                    // Get rid of any parens around the expression if they're not used on the code object by default
                    if (AutomaticFormattingCleanup && !parser.IsGenerated && expression.HasParens && !expression.HasParensDefault)
                        expression.HasParens = false;
                }

                if (expression is TempExpr)
                {
                    // If we got a TempExpr, move any directives as postfix on the previous expression
                    if (list != null && list.Count > 0)
                    {
                        Expression previous = list.Last;
                        foreach (Annotation annotation in expression.Annotations)
                        {
                            if (annotation is CompilerDirective)
                                previous.AttachAnnotation(annotation, AnnotationFlags.IsPostfix);
                        }
                    }
                }
                else if (expression != null || allowSingleNullList || hasComma)
                {
                    if (list == null)
                        list = new ChildList<Expression>(parent);
                    list.Add(expression);
                }

                // Continue processing if we have a ','.  Also treat ';' like a comma if we're parsing an Initializer
                // for better parsing of bad code (such as statements where an expression is expected).
                if (hasComma || (skipStatementTerminators && parser.TokenText == Statement.ParseTokenTerminator))
                {
                    lastCommaFirstOnLine = parser.Token.IsFirstOnLine;
                    parser.NextToken();  // Move past ',' (or ';')
                    if (expression != null)
                    {
                        // Move any EOL comment, and any regular comments as Post comments
                        expression.MoveEOLComment(parser.LastToken, false, false);

                        // Move any following regular comments as Post comments if on a line by themselves
                        if (parser.Token.IsFirstOnLine)
                            expression.MoveCommentsAsPost(parser.LastToken);
                    }
                }
                else
                    break;
            }
            return list;
        }

        /// <summary>
        /// Parse a list of <see cref="Expression"/>s.
        /// </summary>
        public static ChildList<Expression> ParseList(Parser parser, CodeObject parent, string terminator)
        {
            return ParseList(parser, parent, terminator, ParseFlags.None, false);
        }

        /// <summary>
        /// Move any comments from the specified <see cref="Token"/> to the left-most sub-expression.
        /// </summary>
        public virtual void MoveCommentsToLeftMost(Token token, bool skipParens)
        {
            MoveAllComments(token);
        }

        /// <summary>
        /// Parse an <see cref="Expression"/>.
        /// </summary>
        protected Expression(Parser parser, CodeObject parent)
            : base(parser, parent)
        { }

        /// <summary>
        /// Determine if the specified comment should be associated with the current code object during parsing.
        /// </summary>
        public override bool AssociateCommentWhenParsing(CommentBase comment)
        {
            // Only associate regular comments with expressions, not doc comments
            return (comment is Comment);
        }

        #endregion

        #region /* FORMATTING */

        /// <summary>
        /// Determines if the code object only requires a single line for display.
        /// </summary>
        public override bool IsSingleLine
        {
            get { return (base.IsSingleLine && !IsEndFirstOnLine); }
            set
            {
                base.IsSingleLine = value;
                if (value)
                    IsEndFirstOnLine = false;
            }
        }

        /// <summary>
        /// True if the code object defaults to starting on a new line.
        /// </summary>
        public override bool IsFirstOnLineDefault
        {
            get { return HasFirstOnLineAnnotations; }
        }

        /// <summary>
        /// True if the expression should have parens by default.
        /// </summary>
        public virtual bool HasParensDefault
        {
            get { return false; }  // Default is no parens
        }

        /// <summary>
        /// True if the expression is surrounded by parens.
        /// </summary>
        public bool HasParens
        {
            get { return _formatFlags.HasFlag(FormatFlags.Grouping); }
            set
            {
                SetFormatFlag(FormatFlags.Grouping, value);
                _formatFlags |= FormatFlags.GroupingSet;
            }
        }

        /// <summary>
        /// True if the closing paren or bracket is on a new line.
        /// </summary>
        public virtual bool IsEndFirstOnLine
        {
            get { return _formatFlags.HasFlag(FormatFlags.InfixNewLine); }
            set
            { 
                SetFormatFlag(FormatFlags.InfixNewLine, value);
                if (value)
                    _formatFlags |= FormatFlags.NewLinesSet;
            }
        }

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

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

        /// <summary>
        /// Format an expression assigned as an argument to another code object (turns off any parentheses).
        /// </summary>
        public void FormatAsArgument()
        {
            // Clear the grouping (parentheses) flag regardless of if it was manually set
            _formatFlags &= ~(FormatFlags.Grouping | FormatFlags.GroupingSet);
        }

        #endregion

        #region /* RENDERING */

        public override void AsText(CodeWriter writer, RenderFlags flags)
        {
            int newLines = NewLines;
            bool isPrefix = flags.HasFlag(RenderFlags.IsPrefix);
            if (!isPrefix && 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);

            // 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 (hasParens)
            {
                if (IsEndFirstOnLine)
                    writer.WriteLine();
                writer.Write(ParseTokenEndGroup);
            }
            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 (increaseIndent)
                writer.EndIndentation(this);

            if (isPrefix)
            {
                // If this object is rendered as a child prefix object of another, then any whitespace is
                // rendered here *after* the object instead of before it.
                if (newLines > 0)
                    writer.WriteLines(newLines);
                else if (!flags.HasFlag(RenderFlags.NoSpaceSuffix))
                    writer.Write(" ");
            }
        }

        public abstract void AsTextExpression(CodeWriter writer, RenderFlags 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