Click here to Skip to main content
11,478,896 members (63,252 online)
Click here to Skip to main content
Add your own
alternative version

Resolving Symbolic References in a CodeDOM (Part 7)

, 2 Dec 2012 CDDL 5.7K 333 13
Resolving symbolic references in a CodeDOM.
Nova.0.6.exe.zip
Mono.Cecil.dll
Nova.CLI.exe
Nova.CodeDOM.dll
Nova.Examples.exe
Nova.Studio.exe
Nova.Test.exe
Nova.UI.dll
Nova.0.6.zip
Nova.CLI
Properties
Nova.CodeDOM
CodeDOM
Annotations
Base
Comments
Base
DocComments
CodeRef
Base
List
Name
Base
Other
Simple
CompilerDirectives
Base
Conditionals
Base
Messages
Base
Pragmas
Base
Symbols
Base
Base
Interfaces
Expressions
AnonymousMethods
Base
Operators
Base
Binary
Arithmetic
Base
Assignment
Base
Bitwise
Base
Conditional
Relational
Base
Shift
Base
Other
Base
Unary
Base
Other
References
Base
GotoTargets
Base
Methods
Namespaces
Other
Properties
Types
Base
Variables
Base
Projects
Assemblies
Namespaces
References
Base
Statements
Base
Conditionals
Base
Exceptions
Generics
Constraints
Base
Iterators
Base
Jumps
Loops
Methods
OperatorDecls
Miscellaneous
Namespaces
Properties
Base
Events
Types
Base
Variables
Base
Parsing
Base
Properties
Rendering
Resolving
Utilities
Mono.Cecil
Reflection
Nova.Examples
Properties
Nova.Studio
Images
About.png
Configuration.png
EditCopy.png
EditCut.png
EditDelete.png
EditPaste.png
EditRedo.png
EditUndo.png
Error.png
Exit.png
FileNew.png
FileOpen.png
FileSave.png
FileSaveAll.png
FileSaveAs.png
Find.png
Help.png
Info.png
Logo.png
Options.png
Print.png
PrintPreview.png
Properties.png
Todo.png
Warning.png
Objects.ico
Properties
Settings.settings
Nova.Test
Properties
Nova.UI
CodeDOM
Annotations
Base
Comments
Base
DocComments
CodeRef
Base
List
Name
Base
Other
Simple
CompilerDirectives
Base
Conditionals
Base
Messages
Base
Pragmas
Base
Symbols
Base
Base
Expressions
AnonymousMethods
Base
Operators
Base
Binary
Arithmetic
Base
Assignment
Base
Bitwise
Base
Conditional
Relational
Base
Shift
Base
Other
Base
Unary
Base
Other
References
Base
GotoTargets
Base
Methods
Namespaces
Other
Properties
Types
Base
Variables
Base
Projects
Namespaces
References
Base
Statements
Base
Conditionals
Base
Exceptions
Generics
Constraints
Base
Iterators
Base
Jumps
Loops
Methods
OperatorDecls
Miscellaneous
Namespaces
Properties
Base
Events
Types
Base
Variables
Base
Properties
Resolving
Utilties
// 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 Mono.Cecil;

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 = EvaluateType();
                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="TypeReference"/> to an <see cref="Expression"/> (actually, a <see cref="TypeRef"/>).
        /// </summary>
        /// <remarks>This allows <see cref="TypeReference"/>s to be passed directly to any method
        /// expecting an <see cref="Expression"/> type without having to create a reference first.</remarks>
        /// <param name="typeReference">The <see cref="TypeReference"/> to be converted.</param>
        /// <returns>A generated <see cref="TypeRef"/> to the specified <see cref="TypeReference"/>.</returns>
        public static implicit operator Expression(TypeReference typeReference)
        {
            if (typeReference.HasGenericParameters)
            {
                // 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(typeReference, ChildList<Expression>.CreateListOfNulls(typeReference.GenericParameters.Count));
            }
            return TypeRef.Create(typeReference);
        }

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

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

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

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

        /// <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 = EvaluateType();
            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 = EvaluateType();
            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 /* RESOLVING */

        /// <summary>
        /// Evaluate the <see cref="Expression"/> to a type or namespace.
        /// </summary>
        /// <returns>The resulting <see cref="TypeRef"/>, <see cref="UnresolvedRef"/>, or <see cref="NamespaceRef"/>.</returns>
        public override SymbolicRef EvaluateTypeOrNamespace(bool withoutConstants)
        {
            // By default, evaluate to a type - this method is overridden by Lookup, Dot, and NamespaceRef
            // in order to process and return NamespaceRefs in addition to TypeRefBase types.
            return EvaluateType(withoutConstants);
        }

        /// <summary>
        /// Evaluate the type of the <see cref="Expression"/>.
        /// </summary>
        /// <returns>The resulting <see cref="TypeRef"/> or <see cref="UnresolvedRef"/>.</returns>
        public virtual TypeRefBase EvaluateType(bool withoutConstants)
        {
            return null;
        }

        /// <summary>
        /// Evaluate the type of the <see cref="Expression"/>.
        /// </summary>
        /// <returns>The resulting <see cref="TypeRef"/> or <see cref="UnresolvedRef"/>.</returns>
        public TypeRefBase EvaluateType()
        {
            return EvaluateType(false);
        }

        /// <summary>
        /// Find a type argument for the specified type parameter.
        /// </summary>
        public virtual TypeRefBase FindTypeArgument(TypeParameterRef typeParameterRef, CodeObject originatingChild)
        {
            // By default, do nothing - subclasses will override this to provide the appropriate behavior
            return null;
        }

        /// <summary>
        /// Find a type argument for the specified type parameter.
        /// </summary>
        public TypeRefBase FindTypeArgument(TypeParameterRef typeParameterRef)
        {
            return FindTypeArgument(typeParameterRef, null);
        }

        #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)

Share

About the Author

KenBeckett
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).

| Advertise | Privacy | Terms of Use | Mobile
Web04 | 2.8.150520.1 | Last Updated 2 Dec 2012
Article Copyright 2012 by KenBeckett
Everything else Copyright © CodeProject, 1999-2015
Layout: fixed | fluid