// 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 Nova.Parsing;
using Nova.Rendering;
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 /* 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 (_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
}
}