// 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 a local variable declaration.
/// </summary>
public class LocalDecl : VariableDecl
{
#region /* FIELDS */
protected Modifiers _modifiers;
#endregion
#region /* CONSTRUCTORS */
/// <summary>
/// Create a local constant instance.
/// </summary>
/// <param name="name">The name of the constant.</param>
/// <param name="type">The type of the constant.</param>
/// <param name="modifiers">Must be <c>Modifiers.Const</c>.</param>
/// <param name="initialization">The initialization expression for the constant.</param>
public LocalDecl(string name, Expression type, Modifiers modifiers, Expression initialization)
: base(name, type, initialization)
{
_modifiers = modifiers;
}
/// <summary>
/// Create a local variable declaration.
/// </summary>
/// <param name="name">The name of the variable.</param>
/// <param name="type">The type of the variable</param>
/// <param name="initialization">The initialization expression for the variable (optional).</param>
public LocalDecl(string name, Expression type, Expression initialization)
: base(name, type, initialization)
{ }
/// <summary>
/// Create a local variable declaration.
/// </summary>
/// <param name="name">The name of the variable.</param>
/// <param name="type">The type of the variable</param>
public LocalDecl(string name, Expression type)
: base(name, type, null)
{ }
#endregion
#region /* PROPERTIES */
/// <summary>
/// The descriptive category of the code object.
/// </summary>
public override string Category
{
get { return (IsConst ? "local constant" : "local variable"); }
}
/// <summary>
/// Optional <see cref="Modifiers"/>.
/// </summary>
public virtual Modifiers Modifiers
{
get { return _modifiers; }
set
{
if (_parent is MultiLocalDecl)
throw new Exception("Can't directly change the Modifiers of a LocalDecl which is a member of a MultiLocalDecl.");
_modifiers = value;
}
}
/// <summary>
/// The type of the <see cref="LocalDecl"/>.
/// </summary>
public override Expression Type
{
set
{
if (_parent is MultiLocalDecl)
throw new Exception("Can't directly change the Type of a LocalDecl which is a member of a MultiLocalDecl.");
SetField(ref _type, value, true);
}
}
/// <summary>
/// True if the local variable is const.
/// </summary>
public override bool IsConst
{
get { return _modifiers.HasFlag(Modifiers.Const); }
set { _modifiers = (value ? _modifiers | Modifiers.Const : _modifiers & ~Modifiers.Const); }
}
/// <summary>
/// Always <c>false</c> for a local variable.
/// </summary>
public override bool IsStatic
{
get { return false; }
set { }
}
#endregion
#region /* METHODS */
/// <summary>
/// Create a reference to the <see cref="LocalDecl"/>.
/// </summary>
/// <param name="isFirstOnLine">True if the reference should be displayed on a new line.</param>
/// <returns>A <see cref="LocalRef"/>.</returns>
public override SymbolicRef CreateRef(bool isFirstOnLine)
{
return new LocalRef(this, isFirstOnLine);
}
protected internal void SetTypeFromParentMulti(Expression type)
{
SetField(ref _type, type, true);
}
#endregion
#region /* PARSING */
internal static void AddParsePoints()
{
// We detect local variable declarations by a ';', '=', or ',' - we parse backwards from the
// parse-point, and then (in the latter two cases) parse forwards to complete the parsing.
// Use a parse-priority of 100 (FieldDecl uses 0)
Parser.AddParsePoint(ParseTokenTerminator, 100, Parse, typeof(IBlock));
// Use a parse-priority of 100 (FieldDecl uses 0, MultiEnumMemberDecl uses 200, Assignment uses 300)
Parser.AddParsePoint(Assignment.ParseToken, 100, Parse, typeof(IBlock));
// Use a parse-priority of 100 (FieldDecl uses 0, MultiEnumMemberDecl uses 200)
Parser.AddParsePoint(Expression.ParseTokenSeparator, 100, Parse, typeof(IBlock));
}
/// <summary>
/// Parse a <see cref="LocalDecl"/>.
/// </summary>
public static LocalDecl Parse(Parser parser, CodeObject parent, ParseFlags flags)
{
// Validate that we have an unused identifier token preceeded by a type
if (parser.HasUnusedTypeRefAndIdentifier)
return Parse(parser, parent, true, true);
return null;
}
/// <summary>
/// Parse a <see cref="LocalDecl"/>.
/// </summary>
public static LocalDecl Parse(Parser parser, CodeObject parent, bool standAlone, bool allowInitAndMulti)
{
// Parse the first LocalDecl
LocalDecl localDecl = new LocalDecl(parser, parent, standAlone, allowInitAndMulti, true, false);
// Handle additional LocalDecls after any commas
if (!localDecl.HasTerminator && allowInitAndMulti && parser.TokenText == Expression.ParseTokenSeparator)
{
// If it's a multi, create one, and transfer the IsFirstOnLine setting
MultiLocalDecl multiLocalDecl = new MultiLocalDecl(localDecl) { NewLines = localDecl.NewLines, HasTerminator = false };
multiLocalDecl.SetLineCol(localDecl);
localDecl.NewLines = 0;
do
{
Token commaToken = parser.Token;
parser.NextToken(); // Move past ','
// Associate any EOL comment on the ',' to the last LocalDecl
localDecl.MoveEOLComment(commaToken, false, false);
localDecl = new LocalDecl(parser, null, false, true, false, true);
// Force the expression 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 (commaToken.IsFirstOnLine)
localDecl.IsFirstOnLine = true;
// Move any comments after the ',' to the current LocalDecl
localDecl.MoveComments(commaToken);
multiLocalDecl.Add(localDecl);
}
while (parser.TokenText == Expression.ParseTokenSeparator);
localDecl = multiLocalDecl;
if (standAlone)
multiLocalDecl.ParseTerminator(parser);
}
return localDecl;
}
/// <summary>
/// Parse a <see cref="LocalDecl"/>.
/// </summary>
public LocalDecl(Parser parser, CodeObject parent, bool standAlone, bool allowInit, bool hasType, bool isMulti)
: base(parser, parent)
{
if (isMulti)
{
ParseName(parser, parent); // Parse the name
if (allowInit)
ParseInitialization(parser, parent); // Parse the initialization (if any)
}
else
{
if (standAlone)
{
// Parse the name from the Unused list
Token token = parser.RemoveLastUnusedToken();
_name = token.NonVerbatimText;
MoveLocationAndComment(token);
ParseUnusedType(parser, ref _type); // Parse the type from the Unused list
// Parse any modifiers in reverse from the Unused list.
// NOTE: Only 'const' is valid for LocalDecls.
_modifiers = ModifiersHelpers.Parse(parser, this);
if (allowInit)
ParseInitialization(parser, parent); // Parse the initialization (if any)
}
else
{
if (hasType)
ParseType(parser); // Parse the type
ParseName(parser, parent); // Parse the name
if (allowInit)
ParseInitialization(parser, parent); // Parse the initialization (if any)
}
if (standAlone && parser.TokenText != Expression.ParseTokenSeparator)
ParseTerminator(parser);
}
}
protected void ParseName(Parser parser, CodeObject parent)
{
// Parse the name
_name = parser.GetIdentifierText();
if (_name != null)
MoveLocationAndComment(parser.LastToken);
}
/// <summary>
/// Peek ahead at the input tokens to determine if they look like a valid LocalDecl.
/// </summary>
public static bool PeekLocalDecl(Parser parser)
{
bool valid = false;
// Validate that we have what appears to be a valid Type followed by an identifier
if (TypeRefBase.PeekType(parser, parser.Token, false, ParseFlags.Type))
{
Token next = parser.LastPeekedToken;
if (next != null && next.IsIdentifier)
{
// Also validate that it's followed by one of ";=),"
if (";=),".Contains(parser.PeekNextTokenText()))
valid = true;
}
}
return valid;
}
#endregion
#region /* FORMATTING */
/// <summary>
/// Determines if the code object has a terminator character.
/// </summary>
public override bool HasTerminator
{
// Ignore any terminator if we're part of a multi
get { return (!(_parent is MultiLocalDecl) && base.HasTerminator); }
}
#endregion
#region /* RENDERING */
protected override void AsTextPrefix(CodeWriter writer, RenderFlags flags)
{
// If in Description mode, use NoEOLComments to determine if we're being rendered as part of a MultiLocalDecl or not
if (!(_parent is MultiLocalDecl) || (flags.HasFlag(RenderFlags.Description) && !flags.HasFlag(RenderFlags.NoEOLComments)))
ModifiersHelpers.AsText(_modifiers, writer);
}
protected override void AsTextStatement(CodeWriter writer, RenderFlags flags)
{
RenderFlags passFlags = (flags & RenderFlags.PassMask);
bool isDescription = flags.HasFlag(RenderFlags.Description);
// If in Description mode, use NoEOLComments to determine if we're being rendered as part of a MultiLocalDecl or not
if (!(_parent is MultiLocalDecl) || (isDescription && !flags.HasFlag(RenderFlags.NoEOLComments)))
AsTextType(writer, flags);
UpdateLineCol(writer, flags);
writer.WriteIdentifier(_name, flags);
if (_initialization != null)
AsTextInitialization(writer, passFlags);
}
#endregion
}
}