Click here to Skip to main content
Click here to Skip to main content
Add your own
alternative version

Resolving Symbolic References in a CodeDOM (Part 7)

, 2 Dec 2012 CDDL
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.Collections.Generic;

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

namespace Nova.CodeDOM
{
    /// <summary>
    /// Represents a conditional if/then/else (ternary) expression.
    /// </summary>
    public class Conditional : Operator
    {
        #region /* FIELDS */

        protected Expression _if;
        protected Expression _then;
        protected Expression _else;

        #endregion

        #region /* CONSTRUCTORS */

        /// <summary>
        /// Create a <see cref="Conditional"/> operator.
        /// </summary>
        public Conditional(Expression @if, Expression then, Expression @else)
        {
            If = @if;
            Then = then;
            Else = @else;
        }

        #endregion

        #region /* PROPERTIES */

        /// <summary>
        /// The 'if' expression.
        /// </summary>
        public Expression If
        {
            get { return _if; }
            set { SetField(ref _if, value, true); }
        }

        /// <summary>
        /// The 'then' expression.
        /// </summary>
        public Expression Then
        {
            get { return _then; }
            set { SetField(ref _then, value, true); }

        }

        /// <summary>
        /// The 'else' expression.
        /// </summary>
        public Expression Else
        {
            get { return _else; }
            set { SetField(ref _else, value, true); }
        }

        /// <summary>
        /// True if the expression is const.
        /// </summary>
        public override bool IsConst
        {
            // A Conditional is const if both result clauses are const
            get { return (_then != null && _then.IsConst && _else != null && _else.IsConst); }
        }

        #endregion

        #region /* METHODS */

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

        #endregion

        #region /* PARSING */

        /// <summary>
        /// The token used to parse the 'then' part.
        /// </summary>
        public const string ParseToken1 = "?";

        /// <summary>
        /// The token used to parse the 'else' part.
        /// </summary>
        public const string ParseToken2 = ":";

        /// <summary>
        /// The precedence of the operator.
        /// </summary>
        public const int Precedence = 400;

        /// <summary>
        /// True if the operator is left-associative, or false if it's right-associative.
        /// </summary>
        public const bool LeftAssociative = false;

        internal static new void AddParsePoints()
        {
            // Use a parse-priority of 0 (TypeRef uses 100 for nullable types)
            Parser.AddOperatorParsePoint(ParseToken1, Precedence, LeftAssociative, false, Parse);
        }

        /// <summary>
        /// Parse a <see cref="Conditional"/> operator.
        /// </summary>
        public static Conditional Parse(Parser parser, CodeObject parent, ParseFlags flags)
        {
            // Disallow conditionals at the TypeDecl or NamespaceDecl levels - this prevents the possibility of matching when
            // parsing a return type expression that is a nullable type for a generic method that has constraints.
            if (parent is TypeDecl || parent is NamespaceDecl)
                return null;

            // Verify that we have a matching ':' for the '?', otherwise abort (so TypeRef can try parsing it as a nullable type).
            // If we're nested without parens, we must find one extra ':' for each nested level.
            if (PeekConditional(parser, parent, parser.ConditionalNestingLevel + 1, flags))
                return new Conditional(parser, parent);

            return null;
        }

        protected static bool PeekConditional(Parser parser, CodeObject parent, int colonCount, ParseFlags flags)
        {
            // Unfortunately, determining if the '?' is definitely part of a '?:' pair as opposed to a nullable type declaration
            // isn't easy - in fact, it's the single hardest thing to parse in the entire language.  Nicely formatted code always
            // has a space before it in the first case, and not in the second, but code is often poorly formatted.  The only way
            // to be sure how to parse it is to peek ahead looking for the matching ':'.  We can parse in a very simplified manner
            // for efficiency, just keeping track of '()', '[]', '{}' pairs, aborting if we hit a ';' anywhere, or a ',' that's
            // not in a nested scope, or finally if we find the matching ':' (not in a nested scope).  If we're in the '?' clause
            // of a nested Conditional without parens, then we need to find an extra ':' for each nested level (colonCount).
            // One more thing - we have to handle '<>' with generic arguments in order to avoid aborting on a possible ','
            // inside them, but we also have to avoid any confusion with LessThan/GreatherThan operators.

            bool firstToken = true;
            Stack<string> stack = new Stack<string>(8);
            while (true)
            {
                Token next = parser.PeekNextToken();
                if (next == null)
                    break;
            check:
                if (next.IsSymbol)
                {
                    // Abort if any invalid symbols appear immediately after the '?'
                    if (firstToken)
                    {
                        firstToken = false;
                        if (">)]};,".Contains(next.Text))
                            break;
                    }

                    // If we have a '<', skip over any possible generic type parameters so that we don't abort on a ','
                    if (next.Text == TypeRefBase.ParseTokenArgumentStart)
                    {
                        TypeRefBase.PeekTypeArguments(parser, TypeRefBase.ParseTokenArgumentEnd, flags);
                        next = parser.LastPeekedToken;
                    }

                    // Keep track of nested parens, brackets (new arrays), braces (initializers or generics in doc comments)
                    string nextText = next.Text;
                    if (nextText == ParseTokenStartGroup || nextText == NewArray.ParseTokenStart || nextText == Initializer.ParseTokenStart)
                        stack.Push(nextText);
                    else if (nextText == ParseTokenEndGroup)
                    {
                        // If we hit an unexpected ')', abort
                        if (stack.Count == 0 || stack.Peek() != ParseTokenStartGroup)
                            break;
                        stack.Pop();
                    }
                    else if (nextText == NewArray.ParseTokenEnd)
                    {
                        // If we hit an unexpected ']', abort
                        if (stack.Count == 0 || stack.Peek() != NewArray.ParseTokenStart)
                            break;
                        stack.Pop();
                    }
                    else if (nextText == Initializer.ParseTokenEnd)
                    {
                        // If we hit an unexpected '}', abort
                        if (stack.Count == 0 || stack.Peek() != Initializer.ParseTokenStart)
                            break;
                        stack.Pop();
                    }
                    else if (nextText == ParseToken1)
                    {
                        // If we found a '?', recursively call this routine to process it (in order to
                        // differentiate between a nested nullable type or another Conditional).
                        if (!PeekConditional(parser, parent, 1, flags))
                        {
                            // If it wasn't a Conditional, get the last token and check it
                            next = parser.LastPeekedToken;
                            goto check;
                        }
                    }
                    else if (stack.Count == 0)
                    {
                        // We're not inside any nested parens/brackets/braces/angle brackets.  Check for certain symbols:

                        // Terminate on a ',' or ';' (we ignore if nested because of anonymous method bodies)
                        if (nextText == ParseTokenSeparator || nextText == Statement.ParseTokenTerminator)
                            break;

                        // Process a ':'
                        if (nextText == ParseToken2)
                        {
                            // Assume we have a valid Conditional if the expected number of colons has been found
                            if (--colonCount == 0)
                                return true;
                        }
                    }
                }
                else if (next.Text == NewOperator.ParseToken)
                {
                    // If we found a 'new', treat the following as a type (in order to avoid any trailing '?' of
                    // a nullable type from being treated as a nested conditional).
                    TypeRefBase.PeekType(parser, parser.PeekNextToken(), true, flags | ParseFlags.Type);
                    // Whether it worked or not, pick up with the last peeked token
                    next = parser.LastPeekedToken;
                    firstToken = false;
                    goto check;
                }
                firstToken = false;
            }
            return false;
        }

        protected Conditional(Parser parser, CodeObject parent)
            : base(parser, parent)
        {
            IsFirstOnLine = false;
            Expression conditional = parser.RemoveLastUnusedExpression();
            if (conditional != null)
            {
                MoveFormatting(conditional);
                SetField(ref _if, conditional, false);
                if (conditional.IsFirstOnLine)
                    IsFirstOnLine = true;
                // Move any comments before the '?' to the conditional expression
                conditional.MoveCommentsAsPost(parser.LastToken);
            }

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

            Token ifToken = parser.Token;
            parser.NextToken();  // Move past '?'
            ++parser.ConditionalNestingLevel;
            SetField(ref _then, Parse(parser, this, false, ParseToken2), false);
            --parser.ConditionalNestingLevel;
            if (_then != null)
            {
                if (ifToken.IsFirstOnLine)
                    _then.IsFirstOnLine = true;
                _then.MoveCommentsToLeftMost(ifToken, false);
                // Move any comments before the ':' to the then expression
                _then.MoveCommentsAsPost(parser.LastToken);
            }

            Token elseToken = parser.Token;
            ParseExpectedToken(parser, ParseToken2);  // Move past ':'
            SetField(ref _else, Parse(parser, this), false);
            if (_else != null)
            {
                if (elseToken.IsFirstOnLine)
                    _else.IsFirstOnLine = true;
                _else.MoveCommentsToLeftMost(elseToken, false);
                // Move any comments at the end to the else expression
                _else.MoveCommentsAsPost(parser.LastToken);
            }

            // If the else clause isn't on a new line, set the NoIndentation flag
            if ((!elseToken.IsFirstOnLine && (_else == null || !_else.IsFirstOnLine)))
                SetFormatFlag(FormatFlags.NoIndentation, true);
        }

        /// <summary>
        /// Get the precedence of the operator.
        /// </summary>
        public override int GetPrecedence()
        {
            return Precedence;
        }

        /// <summary>
        /// Move any comments from the specified <see cref="Token"/> to the left-most sub-expression.
        /// </summary>
        public override void MoveCommentsToLeftMost(Token token, bool skipParens)
        {
            if ((HasParens && !skipParens) || _if == null)
                MoveAllComments(token);
            else
                _if.MoveCommentsToLeftMost(token, false);
        }

        #endregion

        #region /* RESOLVING */

        /// <summary>
        /// Resolve all child symbolic references, using the specified <see cref="ResolveCategory"/> and <see cref="ResolveFlags"/>.
        /// </summary>
        public override CodeObject Resolve(ResolveCategory resolveCategory, ResolveFlags flags)
        {
            if (_if != null)
                _if = (Expression)_if.Resolve(ResolveCategory.Expression, flags);
            if (_then != null)
                _then = (Expression)_then.Resolve(ResolveCategory.Expression, flags);
            if (_else != null)
                _else = (Expression)_else.Resolve(ResolveCategory.Expression, flags);
            return this;
        }

        /// <summary>
        /// Returns true if the code object is an <see cref="UnresolvedRef"/> or has any <see cref="UnresolvedRef"/> children.
        /// </summary>
        public override bool HasUnresolvedRef()
        {
            if (_if != null && _if.HasUnresolvedRef())
                return true;
            if (_then != null && _then.HasUnresolvedRef())
                return true;
            if (_else != null && _else.HasUnresolvedRef())
                return true;
            return base.HasUnresolvedRef();
        }

        /// <summary>
        /// Evaluate the type of the <see cref="Expression"/>.
        /// </summary>
        /// <returns>The resulting <see cref="TypeRef"/> or <see cref="UnresolvedRef"/>.</returns>
        public override TypeRefBase EvaluateType(bool withoutConstants)
        {
            // Determine the types of the two expressions (ignore 'withoutConstants' here - we must always use them so that
            // we can handle the 'null' constant properly).
            TypeRefBase thenRef = (_then != null ? _then.EvaluateType() : null);
            TypeRefBase elseRef = (_else != null ? _else.EvaluateType() : null);
            if (thenRef == null)
                return elseRef;
            if (elseRef == null)
                return thenRef;

            // Check if the types of both expressions are the same
            if (thenRef.IsSameRef(elseRef))
            {
                // Return a non-constant reference (a conditional can't evaluate to a single constant
                // value, since it has two possible values).
                if (!thenRef.IsConst)
                    return thenRef;
                if (!elseRef.IsConst)
                    return elseRef;
                return thenRef.GetTypeWithoutConstant();
            }

            // Check if the TypeRef of each expression is implicitly convertible to the TypeRef of
            // the other expression.
            // In this particular case, we do NOT want to treat the destination type as 'object' if
            // it's a 'null' literal - we want such a conversion to fail, although the opposite
            // direction ('null' to a reference type) is fine.
            bool thenToElse = (thenRef.IsImplicitlyConvertibleTo(elseRef) && !(_else is Literal && ((Literal)_else).IsNull));
            bool elseToThen = (elseRef.IsImplicitlyConvertibleTo(thenRef) && !(_then is Literal && ((Literal)_then).IsNull));

            // If the first is implicitly convertible to the second, but not vice-versa, use the second type
            if (thenToElse && !elseToThen)
                return (!elseRef.IsConst ? elseRef : elseRef.GetTypeWithoutConstant());

            // If the second is implicitly convertible to the first, but not vice-versa, use the first type
            if (elseToThen && !thenToElse)
                return (!thenRef.IsConst ? thenRef : thenRef.GetTypeWithoutConstant());

            // Although it seems reasonable to resolve to one type if the other is unresolved in order to reduce
            // cascading errors, this can cause a serious problem where member lookups are done somewhere later
            // in the code on the evaluated type of this expression, and are not re-resolved when the evaluated
            // type changes in a 2nd resolve pass due to the unresolved becoming resolved.  Someday, we'll need
            // to automatically re-resolve all member lookups on a type that changes due to code edits, and then
            // perhaps we could add this logic back in IF we do the same thing if its evaluated type changes.
            //// If one of the two types is an UnresolvedRef, use the other type
            //if (thenRef is UnresolvedRef && !(elseRef is UnresolvedRef))
            //    return (!elseRef.IsConst ? elseRef : elseRef.GetTypeWithoutConstant());
            //if (elseRef is UnresolvedRef && !(thenRef is UnresolvedRef))
            //    return (!thenRef.IsConst ? thenRef : thenRef.GetTypeWithoutConstant());

            // No common type could be determined - default to 'object'
            return TypeRef.ObjectRef;
        }

        #endregion

        #region /* FORMATTING */

        /// <summary>
        /// True if the expression should have parens by default.
        /// </summary>
        public override bool HasParensDefault
        {
            get { return true; }
        }

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

        #endregion

        #region /* RENDERING */

        public override void AsTextExpression(CodeWriter writer, RenderFlags flags)
        {
            bool relativeToParent = (!HasNoIndentation && _then != null && _then.IsFirstOnLine && _if != null && _if.IsSingleLine);
            if (relativeToParent)
            {
                writer.SetParentOffset();
                writer.BeginIndentOnNewLineRelativeToParentOffset(this, true);
            }

            if (_if != null)
                _if.AsText(writer, flags);

            RenderFlags thenFlags = flags;
            if (_then != null && _then.IsFirstOnLine)
            {
                writer.WriteLine();
                thenFlags |= RenderFlags.SuppressNewLine;
            }
            else
                writer.Write(" ");
            UpdateLineCol(writer, flags);
            writer.Write("? ");
            if (_then != null)
                _then.AsText(writer, thenFlags);

            if (_else != null && _else.IsFirstOnLine)
            {
                writer.WriteLine();
                writer.Write(": ");
                flags |= RenderFlags.SuppressNewLine;
            }
            else
                writer.Write(" : ");
            if (_else != null)
                _else.AsText(writer, flags);

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

        #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.150302.1 | Last Updated 2 Dec 2012
Article Copyright 2012 by KenBeckett
Everything else Copyright © CodeProject, 1999-2015
Layout: fixed | fluid