// 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 Mono.Cecil;
using Nova.Parsing;
using Nova.Rendering;
using Nova.Resolving;
namespace Nova.CodeDOM
{
/// <summary>
/// Represents the declaration of an individual enum member.
/// Can only be used as a child of <see cref="MultiEnumMemberDecl"/>, not as a stand-alone declaration.
/// </summary>
/// <remarks>
/// The type of an EnumMemberDecl is always the type of its parent MultiEnumMemberDecl (which in
/// turn is always the type of its parent <see cref="EnumDecl"/>).
/// </remarks>
public class EnumMemberDecl : VariableDecl
{
#region /* CONSTRUCTORS */
/// <summary>
/// Create an enum member declaration.
/// </summary>
/// <param name="name">The name of the enum member.</param>
/// <param name="initialization">The initialization expression for the enum member.</param>
public EnumMemberDecl(string name, Expression initialization)
: base(name, null, initialization)
{ }
/// <summary>
/// Create an enum member declaration.
/// </summary>
/// <param name="name">The name of the enum member.</param>
public EnumMemberDecl(string name)
: base(name, null, null)
{ }
#endregion
#region /* PROPERTIES */
/// <summary>
/// The type of the parent <see cref="EnumDecl"/>.
/// </summary>
public override Expression Type
{
get { return (_parent is MultiEnumMemberDecl ? ((MultiEnumMemberDecl)_parent).Type : null); }
set { throw new Exception("Can't change the Type of an EnumMemberDecl - it's always the parent EnumDecl."); }
}
/// <summary>
/// The descriptive category of the code object.
/// </summary>
public override string Category
{
get { return "enum"; }
}
/// <summary>
/// Always <c>true</c> for an enum member.
/// </summary>
public override bool IsConst
{
get { return true; }
set { }
}
/// <summary>
/// Always <c>true</c> for an enum member.
/// </summary>
public override bool IsStatic
{
get { return true; }
set { }
}
/// <summary>
/// The parent <see cref="EnumDecl"/>.
/// </summary>
public virtual EnumDecl ParentEnumDecl
{
get
{
// Our parent should be a MultiEnumMemberDecl, and our grandparent is the EnumDecl
return (_parent is MultiEnumMemberDecl ? _parent.Parent as EnumDecl : null);
}
}
/// <summary>
/// True if this is a member of a bit-flag enum.
/// </summary>
public bool IsBitFlag
{
get
{
EnumDecl enumDecl = ParentEnumDecl;
return (enumDecl != null && enumDecl.IsBitFlags);
}
}
#endregion
#region /* METHODS */
/// <summary>
/// Create a reference to the <see cref="EnumMemberDecl"/>.
/// </summary>
/// <param name="isFirstOnLine">True if the reference should be displayed on a new line.</param>
/// <returns>A <see cref="EnumMemberRef"/>.</returns>
public override SymbolicRef CreateRef(bool isFirstOnLine)
{
return new EnumMemberRef(this, isFirstOnLine);
}
/// <summary>
/// Get the full name of the <see cref="EnumMemberDecl"/>, including the namespace name.
/// </summary>
/// <param name="descriptive">True to display type parameters and method parameters, otherwise false.</param>
public override string GetFullName(bool descriptive)
{
EnumDecl enumDecl = ParentEnumDecl;
if (enumDecl != null)
return enumDecl.GetFullName(descriptive) + "." + _name;
return _name;
}
#endregion
#region /* PARSING */
internal static void AddParsePoints()
{
// We detect enum member declarations by '=' or ',' at the top level of an EnumDecl block.
// We parse backwards from the parse-point, and then parse forwards to complete the parsing.
// This is consistent with how we parse LocalDecls, but it doesn't handle a single-name enum,
// so we do a special check for that in EnumDecl/Parser. Unlike LocalDecls and FieldDecls,
// EnumMemberDecls can't exist independently, but only as part of a MultiEnumMemberDecl.
// However, we parse them here to be consistent with how the others work, and to avoid the
// issue of a parse constructor for MultiEnumMemberDecl having to call the EnumMemberDecl
// parse constructor.
// Use a parse-priority of 200 (FieldDecl uses 0, LocalDecl uses 100, Assignment uses 300)
Parser.AddParsePoint(Assignment.ParseToken, 200, Parse, typeof(EnumDecl));
// Use a parse-priority of 200 (FieldDecl uses 0, LocalDecl uses 100)
Parser.AddParsePoint(Expression.ParseTokenSeparator, 200, Parse, typeof(EnumDecl));
}
/// <summary>
/// Parse an <see cref="EnumMemberDecl"/>.
/// </summary>
public static EnumMemberDecl Parse(Parser parser, CodeObject parent, ParseFlags flags)
{
// Turn off special callback from EnumDecl used to parse single-identifier enums
parser.SingleUnusedIdentifierParser = null;
// Validate that we have an unused identifier token
if (parser.HasUnusedIdentifier)
{
// Parse the first EnumMemberDecl
EnumMemberDecl enumMemberDecl = new EnumMemberDecl(parser, parent, true);
// Always create a MultiEnumMemberDecl for enums
MultiEnumMemberDecl multiEnumMemberDecl = new MultiEnumMemberDecl(enumMemberDecl);
multiEnumMemberDecl.SetLineCol(enumMemberDecl);
// Handle additional EnumMemberDecls after any commas
while (parser.TokenText == Expression.ParseTokenSeparator)
{
Token lastTokenOfLastMember = parser.LastToken;
bool lastCommaFirstOnLine = parser.Token.IsFirstOnLine;
parser.NextToken(); // Move past ','
// Associate any EOL comment on the ',' to the last EnumMemberDecl
enumMemberDecl.MoveEOLComment(parser.LastToken, false, false);
// Parse any comments, doc comments, attributes, compiler directives
enumMemberDecl.ParseAnnotations(parser, parent, false, false);
// Abort if we had a trailing comma with nothing after it
if (parser.TokenText == Block.ParseTokenEnd)
break;
// Parse the next EnumMemberDecl
enumMemberDecl = new EnumMemberDecl(parser, null, false);
// Get any post comments after the last member and before the comma
enumMemberDecl.MoveComments(lastTokenOfLastMember);
// Force the EnumMemberDecl 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)
enumMemberDecl.IsFirstOnLine = true;
multiEnumMemberDecl.Add(enumMemberDecl);
}
// Parse any post compiler directives
enumMemberDecl.ParseAnnotations(parser, parent, true, false);
return multiEnumMemberDecl;
}
return null;
}
/// <summary>
/// Parse an <see cref="EnumMemberDecl"/> starting from an unused name.
/// </summary>
public static EnumMemberDecl ParseEnd(Parser parser, CodeObject parent, ParseFlags flags)
{
if (parser.HasUnusedIdentifier)
return Parse(parser, parent, flags);
return null;
}
/// <summary>
/// Parse an <see cref="EnumMemberDecl"/>.
/// </summary>
public EnumMemberDecl(Parser parser, CodeObject parent, bool unusedName)
: base(parser, parent)
{
Token token;
if (unusedName)
{
// Get the name from the Unused list
token = parser.RemoveLastUnusedToken();
_name = token.NonVerbatimText;
}
else
{
// Parse the name
_name = parser.GetIdentifierText();
token = parser.LastToken;
}
MoveLocationAndComment(token);
ParseUnusedAnnotations(parser, this, true); // Parse any annotations from the Unused list
ParseInitialization(parser, parent); // Parse the initialization (if any)
// Move any EOL or Postfix annotations on the init expression to the parent
if (_initialization != null)
MoveEOLAndPostAnnotations(_initialization);
}
#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 ((flags & (ResolveFlags.Phase1 | ResolveFlags.Phase3)) == 0)
{
// Resolve the type before the Initialization, so it can be used to avoid ambiguities
// while resolving the Initialization (such as for method groups).
if (_type != null)
_type = (Expression)_type.Resolve(ResolveCategory.Type, flags);
}
if ((flags & (ResolveFlags.Phase1 | ResolveFlags.Phase2)) == 0)
{
ResolveAttributes(flags);
if (_initialization != null)
_initialization = (Expression)_initialization.Resolve(ResolveCategory.Expression, flags);
ResolveDocComments(flags);
}
return this;
}
/// <summary>
/// Resolve child code objects that match the specified name.
/// </summary>
public virtual void ResolveRef(string name, Resolver resolver)
{
if (Name == name)
resolver.AddMatch(this);
}
/// <summary>
/// Evaluate the type of the enum member declaration.
/// </summary>
/// <remarks>This method evaluates the Type expression into a TypeRefBase, which will properly evaluate the type arguments
/// of nested types. It also handles constants and the Type being null.</remarks>
public override TypeRefBase EvaluateType(bool withoutConstants)
{
TypeRefBase typeRefBase = base.EvaluateType(withoutConstants);
if (typeRefBase is TypeRef && !withoutConstants)
{
object value = GetValue();
TypeRef typeRef = (TypeRef)typeRefBase;
if (typeRef.IsEnum || value == null)
return new TypeRef(typeRef, value);
return new TypeRef(value);
}
return typeRefBase;
}
/// <summary>
/// Get the constant value of the variable (if any).
/// </summary>
/// <returns>An object of the type of the variable with a value of the constant used
/// to initialize it (if any), or null if the variable isn't a constant.</returns>
public override object GetValue()
{
if (_initialization == null)
{
// If we don't have an initialization value, calculate our value from any previous enum members
MultiEnumMemberDecl parentMulti = _parent as MultiEnumMemberDecl;
if (parentMulti != null)
{
// Determine the type of the enum and the index of the current member
object reference = parentMulti.UnderlyingType.EvaluateType().Reference;
Type type;
if (reference is TypeDefinition)
type = TypeRef.GetEquivalentType((TypeDefinition)reference);
else if (reference is Type)
type = (Type)reference;
else
type = typeof(int);
ChildList<EnumMemberDecl> memberDecls = parentMulti.MemberDecls;
int index = memberDecls.IndexOf(this);
object value = null;
// Work backwards until we find an enum member with an initialization, or the first one
int preIndex = index;
while (preIndex > 0)
{
--preIndex;
if (memberDecls[preIndex].HasInitialization)
{
value = memberDecls[preIndex].GetValue();
break;
}
}
if (value == null)
value = Activator.CreateInstance(type);
// Move forwards, incrementing the value
while (preIndex < index)
{
// Increment the value instance
switch (System.Type.GetTypeCode(type))
{
case TypeCode.UInt64: value = (ulong)value + 1; break;
case TypeCode.Int64: value = (long)value + 1; break;
case TypeCode.UInt32: value = (uint)value + 1; break;
case TypeCode.Int32: value = (int)value + 1; break;
case TypeCode.UInt16: value = (ushort)((ushort)value + 1); break;
case TypeCode.Int16: value = (short)((short)value + 1); break;
case TypeCode.Byte: value = (byte)((byte)value + 1); break;
case TypeCode.SByte: value = (sbyte)((sbyte)value + 1); break;
}
++preIndex;
}
return value;
}
}
return base.GetValue();
}
#endregion
#region /* FORMATTING */
/// <summary>
/// True if the code object only requires a single line for display by default.
/// </summary>
public override bool IsSingleLineDefault
{
get { return !HasFirstOnLineAnnotations; }
}
/// <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 <see cref="Statement"/> has a terminator character by default.
/// </summary>
public override bool HasTerminatorDefault
{
get { return false; }
}
/// <summary>
/// Determines if the code object has a terminator character.
/// </summary>
public override bool HasTerminator
{
// EnumMemberDecls don't have terminators, so disable use of this flag
get { return false; }
set { }
}
/// <summary>
/// Determine a default of 1 or 2 newlines when adding items to a <see cref="Block"/>.
/// </summary>
public override int DefaultNewLines(CodeObject previous)
{
// Default to a preceeding blank line if the object has first-on-line annotations, or if
// it's not another enum member declaration.
if (HasFirstOnLineAnnotations || !(previous is EnumMemberDecl))
return 2;
return 1;
}
/// <summary>
/// The number of newlines preceeding the object (0 to N).
/// </summary>
public override int NewLines
{
get { return base.NewLines; }
set
{
// If we're changing to or from zero, also change any prefix attributes
bool isFirstOnLine = (value != 0);
if (_annotations != null && ((!isFirstOnLine && IsFirstOnLine) || (isFirstOnLine && !IsFirstOnLine)))
{
foreach (Annotation annotation in _annotations)
{
if (annotation is Attribute)
annotation.IsFirstOnLine = isFirstOnLine;
}
}
base.NewLines = value;
}
}
#endregion
#region /* RENDERING */
protected override void AsTextStatement(CodeWriter writer, RenderFlags flags)
{
RenderFlags passFlags = (flags & RenderFlags.PassMask);
bool isDescription = flags.HasFlag(RenderFlags.Description);
UpdateLineCol(writer, flags);
if (isDescription)
{
EnumDecl parentEnumDecl = ParentEnumDecl;
if (parentEnumDecl != null)
{
parentEnumDecl.AsTextName(writer, flags);
Dot.AsTextDot(writer);
}
}
writer.Write(_name);
if (isDescription)
AsTextConstantValue(writer, passFlags | (IsBitFlag ? RenderFlags.FormatAsHex : 0));
else if (_initialization != null)
{
// Check for alignment of the initialization (ignore if empty or it doesn't fit the pattern)
if (IsBitFlag && IsFirstOnLine)
{
int padding = writer.GetColumnWidth(Parent, 0) - _name.Length;
if (padding > 0)
writer.Write(new string(' ', padding));
}
AsTextInitialization(writer, passFlags);
}
}
#endregion
}
}