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