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;
using System.Collections.Generic;

using Nova.Parsing;
using Nova.Rendering;

namespace Nova.CodeDOM
{
    /// <summary>
    /// These modifiers are usable on various code objects to specify access and special behaviors.
    /// </summary>
    /// <remarks>
    /// The order of appearance in this enum determines the display order of the modifiers.
    /// </remarks>
    [Flags]
    public enum Modifiers
    {
        //                          * = only if nested                                 Event,
        None      = 0x00000000,  // Class  Struct  Interface  Delegate  Enum  Method  Property  Indexer  Field  Constant  Ctor  Operator  Accessor  Destructor
        Public    = 0x00000001,  //   Y       Y        Y         Y       Y       Y        Y        Y       Y       Y       Y       Y
        Protected = 0x00000002,  //   *       *        *         *       *       Y        Y        Y       Y       Y       Y                 Y
        Internal  = 0x00000004,  //   Y       Y        Y         Y       Y       Y        Y        Y       Y       Y       Y                 Y
        // Protected + Internal  //   *       *        *         *       *       Y        Y        Y       Y       Y       Y                 Y
        Private   = 0x00000008,  //   *       *        *         *       *       Y        Y        Y       Y       Y       Y                 Y
        Static    = 0x00000010,  //   Y                                          Y        Y                Y               Y       Y
        New       = 0x00000020,  //   *                          *       *       Y        Y        Y       Y       Y
        Abstract  = 0x00000040,  //   Y                                          Y        Y        Y
        Sealed    = 0x00000080,  //   Y                                          Y        Y        Y
        Virtual   = 0x00000100,  //                                              Y        Y        Y
        Override  = 0x00000200,  //                                              Y        Y        Y
        Extern    = 0x00000400,  //                                              Y        Y        Y                       Y       Y                    Y
        Unsafe    = 0x00000800,  //   Y       Y        Y         Y               Y        Y        Y       Y       Y       Y       Y         Y
        // NOTE: Partial must appear as the last modifier for both types and methods.
        Partial   = 0x00001000,  //   Y       Y        Y                         Y
        Implicit  = 0x00004000,  //                                                                                                Y (conv ops only)
        Explicit  = 0x00008000,  //                                                                                                Y (conv ops only)
        Const     = 0x00010000,  //                                                                                Y (also local vars)
        ReadOnly  = 0x00020000,  //                                                                        Y
        Volatile  = 0x00040000,  //                                                                        Y
        Event     = 0x00100000   //                                                                        Y

        // RULES:
        // - Namespaces have no access modifiers (they are implicitly public).
        // - Top-level type declarations can be only 'internal' or 'public', and default to 'internal'.
        // - Nested type declarations in a class or struct default to 'private'.
        // - Interface and enum members are implicitly 'public' (no access modifiers are allowed).
        // - Class can't be both 'abstract' and 'sealed'.
        // - If any method of a class is 'abstract', the class must be 'abstract'.
        // - A non-abstract Class with an 'abstract' base must implement all 'abstract' members.
        // - An 'abstract' class must implement all interface members, although it may map them onto 'abstract' methods.
        // - Structs are implicitly 'sealed', and can't have default constructors or a destructor.
        // - Struct members can't have 'protected' or 'protected internal' access.
        // - An 'abstract' method can't be 'static', 'virtual' (it's implicitly virtual), or 'extern'.
        // - An 'abstract' or 'extern' method has no body.
        // - An 'abstract' property behaves like an 'abstract' method.
        // - An 'abstract' property can't be 'static'.
        // - Nested types can access 'private' members of their parent.
        // - A 'readonly' field may only be assigned in the declaration or in a constructor of the parent class.
        // - 'const' or type declaration members of a class are implicitly 'static'.
        // - 'const' reference types can only be null or a string.
        // - 'virtual' can't be used with 'static', 'abstract', or 'override'.
        // - 'volatile' fields must be a reference type, or an integral type (excluding long/ulong), or a float.
    }

    #region /* STATIC HELPER CLASS */

    /// <summary>
    /// Static helper methods for Modifiers.
    /// </summary>
    public static class ModifiersHelpers
    {
        #region /* STATIC FIELDS */

        private static readonly string[] _names;
        private static readonly Array _values;
        private static readonly Dictionary<string, Modifiers> _nameToModifierMap = new Dictionary<string, Modifiers>();

        #endregion

        #region /* STATIC CONSTRUCTOR */

        // Setup arrays of names, values, and a map of names to values.
        static ModifiersHelpers()
        {
            _names = Enum.GetNames(typeof(Modifiers));
            _values = Enum.GetValues(typeof(Modifiers));
            for (int i = 0; i < _values.Length; ++i)
            {
                _names[i] = _names[i].ToLower();
                _nameToModifierMap.Add(_names[i], (Modifiers)_values.GetValue(i));
            }
        }

        #endregion

        #region /* STATIC HELPER METHODS */

        /// <summary>
        /// Format Modifiers as a string.
        /// </summary>
        public static string AsString(Modifiers modifiers)
        {
            if (modifiers == Modifiers.None)
                return "";
            using (CodeWriter writer = new CodeWriter())
            {
                AsText(modifiers, writer);
                return writer.ToString();
            }
        }

        /// <summary>
        /// Convert Modifiers to text.
        /// </summary>
        public static void AsText(Modifiers modifiers, CodeWriter writer)
        {
            if (modifiers != Modifiers.None)
            {
                for (int i = 1; i < _values.Length; ++i)
                {
                    if (modifiers.HasFlag((Modifiers)_values.GetValue(i)))
                        writer.Write(_names[i] + ' ');
                }
            }
        }

        /// <summary>
        /// Returns true if the specified text is a valid modifier name.
        /// </summary>
        public static bool IsModifier(string modifier)
        {
            return (modifier != null && _nameToModifierMap.ContainsKey(modifier));
        }

        #endregion

        #region /* PARSING */

        /// <summary>
        /// Parse tokens into Modifiers bit flags.
        /// </summary>
        public static Modifiers Parse(Parser parser, CodeObject parent)
        {
            // Search the parser's unused list for valid modifier tokens
            Modifiers modifiers = 0;
            bool hasSandwichedModifiers = false;
            bool hasConditionalModifiers = false;
            bool needsElseCondition = true;
            bool elseIsNotActive = false;
            bool foundEndIf = false;
            List<ParsedObject> unusedList = parser.Unused;

            // First, pre-scan the unused list to check for the special case of conditional directives
            // being used on the modifiers.  Set a flag if we detect this for later processing below.
            for (int i = unusedList.Count - 1; i >= 0; --i)
            {
                ParsedObject parsedObject = unusedList[i];
                if (parsedObject is Token)
                {
                    // Abort if there's a blank line, or if we get a token that isn't a modifier
                    Token token = (Token)parsedObject;
                    if (token.NewLines > 1 || !IsModifier(token.Text))
                        break;
                    if (foundEndIf)
                        hasSandwichedModifiers = true;
                }
                else
                {
                    // Abort if we get anything other than the expected #if/#else/#endif chain, or if
                    // we have any comments before we hit the #endif (going backwards).
                    CodeObject codeObject = ((UnusedCodeObject)parsedObject).CodeObject;
                    if (codeObject is EndIfDirective)
                    {
                        if (foundEndIf || parsedObject.HasTrailingComments)
                            break;
                        foundEndIf = true;
                    }
                    else if (codeObject is ConditionalDirective)
                    {
                        if (!foundEndIf)
                            break;
                        if (codeObject is ElseDirective)
                            needsElseCondition = false;
                        else
                        {
                            if (((ConditionalDirective)codeObject).IsActive)
                                elseIsNotActive = true;
                            if (codeObject is IfDirective)
                            {
                                if (hasSandwichedModifiers)
                                    hasConditionalModifiers = true;
                                break;
                            }
                        }
                    }
                    else
                        break;
                }
            }

            // Now, reset and do the real parse scan
            bool postDirective = true;
            string declarationText = null;
            string declarationModifiersText = null;
            Token lastUnrecognizedToken = null;
            List<ConditionalDirective> inactiveConditions = new List<ConditionalDirective>();
            for (int i = unusedList.Count - 1; i >= 0; --i)
            {
                ParsedObject parsedObject = unusedList[i];
                if (parsedObject is Token)
                {
                    // Check for modifier tokens
                    Token token = (Token)unusedList[i];
                    Modifiers value;
                    if (_nameToModifierMap.TryGetValue(token.Text, out value))
                    {
                        modifiers |= value;
                        parent.MoveFormatting(token);
                        parent.MoveAllComments(token, true);
                        unusedList.RemoveAt(i);

                        // If we've passed the conditionals and have prefixed modifiers, add them
                        // to the skipped text of all of the inactive conditions.
                        if (!hasConditionalModifiers && inactiveConditions.Count > 0)
                        {
                            foreach (ConditionalDirective conditionalDirective in inactiveConditions)
                                conditionalDirective.SkippedText = AsString(value) + conditionalDirective.SkippedText;
                        }
                    }
                    else
                    {
                        // If the token isn't recognized, continue processing in case there are other unused tokens
                        // that we do recognize as modifiers (there might be new modifiers added in later C# versions).
                        // Also, keep track of the last unrecognized token so we can transfer any newlines to it.
                        lastUnrecognizedToken = (Token)parsedObject;
                    }
                }
                else if (hasConditionalModifiers)
                {
                    // Check for compiler directives - specifically, we handle an #endif immediately
                    // preceeding the parent declaration, allowing for a chain of conditional directives
                    // used to change the modifiers on the declaration at compile-time.  We have already
                    // verified that we have a valid sequence (above), so we process conditional directives
                    // backwards here until we get to the starting #if.
                    UnusedCodeObject unused = (UnusedCodeObject)parsedObject;
                    if (unused.CodeObject is CompilerDirective)
                    {
                        // Store any compiler directives as pre or post annotations on the parent
                        CompilerDirective compilerDirective = (CompilerDirective)unused.CodeObject;
                        if (compilerDirective is EndIfDirective)
                        {
                            declarationText = parent.AsString();
                            declarationModifiersText = AsString(modifiers);
                        }
                        else if (compilerDirective is ConditionalDirective)
                        {
                            ConditionalDirective conditionalDirective = (ConditionalDirective)compilerDirective;

                            // Create an #else if none existed
                            if (needsElseCondition)
                            {
                                ElseDirective elseDirective = new ElseDirective();
                                if (elseIsNotActive)
                                {
                                    elseDirective.SkippedText = declarationText;
                                    inactiveConditions.Add(elseDirective);
                                }
                                else
                                    postDirective = false;
                                parent.AttachAnnotation(elseDirective, elseIsNotActive ? AnnotationFlags.IsPostfix : AnnotationFlags.None, true);
                                needsElseCondition = false;
                            }

                            // When we find the active condition, switch from post to pre
                            if (conditionalDirective.IsActive)
                                postDirective = false;
                            else
                            {
                                // Update any skipped conditions to reflect the entire declaration
                                Modifiers orderedModifiers = Parse(conditionalDirective.SkippedText + " " + declarationModifiersText);
                                conditionalDirective.SkippedText = AsString(orderedModifiers) + declarationText;
                                inactiveConditions.Add(conditionalDirective);
                            }

                            // Stop processing conditionals when we get to the #if
                            if (compilerDirective is IfDirective)
                                hasConditionalModifiers = false;
                        }
                        else
                            break;  // For now, stop if we find any other directives

                        parent.AttachAnnotation(compilerDirective, postDirective ? AnnotationFlags.IsPostfix : AnnotationFlags.None, true);
                        parent.MoveComments(unused.LastToken, true);
                        unusedList.RemoveAt(i);
                    }
                }
                else
                    break;
            }

            // If we had any unrecognized tokens, they'll be emitted before the parent object, so if the last one
            // doesn't have any newlines, we need to give it the parent's newlines.
            if (lastUnrecognizedToken != null)
            {
                if (lastUnrecognizedToken.NewLines == 0)
                    lastUnrecognizedToken.NewLines = (ushort)parent.NewLines;
                parent.NewLines = 0;
            }

            return modifiers;
        }

        /// <summary>
        /// Parse a string of space-delimited modifiers.
        /// </summary>
        public static Modifiers Parse(string input)
        {
            Modifiers modifiers = 0;
            foreach (string modifier in input.Split(' '))
            {
                Modifiers value;
                if (_nameToModifierMap.TryGetValue(modifier, out value))
                    modifiers |= value;
            }
            return modifiers;
        }

        #endregion
    }

    #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 | Mobile
Web03 | 2.8.141015.1 | Last Updated 2 Dec 2012
Article Copyright 2012 by KenBeckett
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid