// 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.Linq;
using Nova.Parsing;
using Nova.Rendering;
using Nova.Resolving;
namespace Nova.CodeDOM
{
/// <summary>
/// The common base class of <see cref="PropertyDecl"/>, <see cref="IndexerDecl"/>, and <see cref="EventDecl"/>.
/// </summary>
public abstract class PropertyDeclBase : BlockStatement, IVariableDecl, IModifiers
{
#region /* FIELDS */
protected Modifiers _modifiers;
/// <summary>
/// The return type is an <see cref="Expression"/> that must evaluate to a <see cref="TypeRef"/> in valid code.
/// </summary>
protected Expression _type;
/// <summary>
/// The name can be a string or an Expression (in which case it should be a Dot operator
/// with a TypeRef to an Interface on the left and an interface member ref on the right), or if this
/// is an IndexerDecl it can be a ThisRef or a Dot operator with a TypeRef to an Interface on the
/// left and ThisRef on the right.
/// </summary>
protected object _name;
#endregion
#region /* CONSTRUCTORS */
protected PropertyDeclBase(string name, Expression type, Modifiers modifiers, CodeObject body)
: base(body, true)
{
_name = name;
Type = type;
_modifiers = modifiers;
}
protected PropertyDeclBase(string name, Expression type, Modifiers modifiers)
: this(name, type, modifiers, new Block())
{ }
protected PropertyDeclBase(string name, Expression type)
: this(name, type, Modifiers.None)
{ }
protected PropertyDeclBase(Expression name, Expression type, Modifiers modifiers)
: this(null, type, modifiers)
{
_name = name;
}
#endregion
#region /* PROPERTIES */
/// <summary>
/// The name of the property.
/// </summary>
public virtual string Name
{
get
{
if (_name is string)
return (string)_name;
// If it's an explicit interface implementation, use the full name
if (_name is Expression)
return ((Expression)_name).AsString();
return null;
}
set { _name = value; }
}
/// <summary>
/// The descriptive category of the code object.
/// </summary>
public abstract string Category { get; }
/// <summary>
/// The type of the property.
/// </summary>
public Expression Type
{
get { return _type; }
set { SetField(ref _type, value, true); }
}
/// <summary>
/// True if the property is readable.
/// </summary>
public abstract bool IsReadable { get; }
/// <summary>
/// True if the property is writable.
/// </summary>
public abstract bool IsWritable { get; }
/// <summary>
/// Optional <see cref="Modifiers"/> for the property.
/// </summary>
public Modifiers Modifiers
{
get { return _modifiers; }
set { _modifiers = value; }
}
/// <summary>
/// True if the property is abstract.
/// </summary>
public bool IsAbstract
{
get { return _modifiers.HasFlag(Modifiers.Abstract); }
set { _modifiers = (value ? _modifiers | Modifiers.Abstract : _modifiers & ~Modifiers.Abstract); }
}
/// <summary>
/// True if the property is static.
/// </summary>
public bool IsStatic
{
get { return _modifiers.HasFlag(Modifiers.Static); }
set { _modifiers = (value ? _modifiers | Modifiers.Static : _modifiers & ~Modifiers.Static); }
}
/// <summary>
/// True if the property has public access.
/// </summary>
public bool IsPublic
{
get { return _modifiers.HasFlag(Modifiers.Public); }
// Force other flags off if setting to Public
set { _modifiers = (value ? _modifiers & ~(Modifiers.Private | Modifiers.Protected | Modifiers.Internal) | Modifiers.Public : _modifiers & ~Modifiers.Public); }
}
/// <summary>
/// True if the property has private access.
/// </summary>
public bool IsPrivate
{
get { return _modifiers.HasFlag(Modifiers.Private); }
// Force other flags off if setting to Private
set { _modifiers = (value ? _modifiers & ~(Modifiers.Protected | Modifiers.Internal | Modifiers.Public) | Modifiers.Private : _modifiers & ~Modifiers.Private); }
}
/// <summary>
/// True if the property has protected access.
/// </summary>
public bool IsProtected
{
get { return _modifiers.HasFlag(Modifiers.Protected); }
// Force certain other flags off if setting to Protected
set { _modifiers = (value ? _modifiers & ~(Modifiers.Private | Modifiers.Public) | Modifiers.Protected : _modifiers & ~Modifiers.Protected); }
}
/// <summary>
/// True if the property has internal access.
/// </summary>
public bool IsInternal
{
get { return _modifiers.HasFlag(Modifiers.Internal); }
// Force certain other flags off if setting to Protected
set { _modifiers = (value ? _modifiers & ~(Modifiers.Private | Modifiers.Public) | Modifiers.Internal : _modifiers & ~Modifiers.Internal); }
}
/// <summary>
/// True if the method is an override.
/// </summary>
public bool IsOverride
{
get { return _modifiers.HasFlag(Modifiers.Override); }
set { _modifiers = (value ? _modifiers | Modifiers.Override : _modifiers & ~Modifiers.Override); }
}
/// <summary>
/// True if the property is virtual.
/// </summary>
public bool IsVirtual
{
get { return _modifiers.HasFlag(Modifiers.Virtual); }
set { _modifiers = (value ? _modifiers | Modifiers.Virtual : _modifiers & ~Modifiers.Virtual); }
}
/// <summary>
/// True if this is an explicit interface implementation.
/// </summary>
public bool IsExplicitInterfaceImplementation
{
get { return _name is Dot; }
}
/// <summary>
/// Get the explicit interface expression (if any).
/// </summary>
public Expression ExplicitInterfaceExpression
{
get { return _name as Expression; }
}
/// <summary>
/// Get the declaring <see cref="TypeDecl"/>.
/// </summary>
public TypeDecl DeclaringType
{
get { return (_parent as TypeDecl); }
}
#endregion
#region /* METHODS */
/// <summary>
/// Add the <see cref="CodeObject"/> to the specified dictionary.
/// </summary>
public virtual void AddToDictionary(NamedCodeObjectDictionary dictionary)
{
dictionary.Add(Name, this);
}
/// <summary>
/// Remove the <see cref="CodeObject"/> from the specified dictionary.
/// </summary>
public virtual void RemoveFromDictionary(NamedCodeObjectDictionary dictionary)
{
dictionary.Remove(Name, this);
}
/// <summary>
/// Get the access rights of the property.
/// </summary>
public abstract void GetAccessRights(bool isTargetOfAssignment, out bool isPrivate, out bool isProtected, out bool isInternal);
/// <summary>
/// Deep-clone the code object.
/// </summary>
public override CodeObject Clone()
{
PropertyDeclBase clone = (PropertyDeclBase)base.Clone();
clone.CloneField(ref clone._type, _type);
clone.CloneField(ref clone._name, _name);
return clone;
}
/// <summary>
/// Get the full name of the <see cref="INamedCodeObject"/>, including any namespace name.
/// </summary>
/// <param name="descriptive">True to display type parameters and method parameters, otherwise false.</param>
public virtual string GetFullName(bool descriptive)
{
if (_parent is TypeDecl)
return ((TypeDecl)_parent).GetFullName(descriptive) + "." + _name;
return _name.ToString();
}
/// <summary>
/// Get the full name of the <see cref="INamedCodeObject"/>, including any namespace name.
/// </summary>
public string GetFullName()
{
return GetFullName(false);
}
#endregion
#region /* PARSING */
internal static void AddParsePoints()
{
// Property declarations are only valid with a TypeDecl parent, but we'll allow any IBlock so that we can
// properly parse them if they accidentally end up at the wrong level (only to flag them as errors).
// This also allows for them to be embedded in a DocCode object.
// Use a parse-priority of 200 (GenericMethodDecl uses 0, UnresolvedRef uses 100, BlockDecl uses 300, Initializer uses 400)
Parser.AddParsePoint(Block.ParseTokenStart, 200, Parse, typeof(IBlock));
}
/// <summary>
/// Parse a <see cref="PropertyDecl"/> or <see cref="EventDecl"/>.
/// </summary>
public static PropertyDeclBase Parse(Parser parser, CodeObject parent, ParseFlags flags)
{
// If our parent is a TypeDecl, verify that we have an unused Expression (it can be either an
// identifier or a Dot operator for explicit interface implementations). Otherwise, require a
// possible type in addition to the Expression.
// If it doesn't seem to match the proper pattern, abort so that other types can try parsing it.
if ((parent is TypeDecl && parser.HasUnusedExpression) || parser.HasUnusedTypeRefAndExpression)
{
// If we have an unused 'event' modifier, it's an event, otherwise treat it as a property
string eventModifier = ModifiersHelpers.AsString(Modifiers.Event).Trim();
bool isEvent = (Enumerable.Any(parser.Unused, delegate(ParsedObject parsedObject) { return parsedObject is Token && ((Token)parsedObject).Text == eventModifier; }));
return (isEvent ? (PropertyDeclBase)new EventDecl(parser, parent) : new PropertyDecl(parser, parent));
}
return null;
}
protected PropertyDeclBase(Parser parser, CodeObject parent, bool parse)
: base(parser, parent)
{
IsFirstOnLine = true; // Force all property, indexer, and event declarations to start on a new line
if (parse)
{
// Parse the name
if (parser.HasUnusedIdentifier)
{
Token token = parser.RemoveLastUnusedToken();
_name = token.NonVerbatimText;
SetLineCol(token);
}
else
{
// Support Dot expressions so we can handle explicit interface members
Expression expression = parser.RemoveLastUnusedExpression();
SetField(ref _name, expression, false);
Expression leftExpression = (expression is BinaryOperator ? ((BinaryOperator)expression).Left : expression);
if (leftExpression != null)
SetLineCol(leftExpression);
}
ParseTypeModifiersAnnotations(parser); // Parse type and any modifiers and/or attributes
new Block(out _body, parser, this, true); // Parse the body
}
}
protected void ParseTypeModifiersAnnotations(Parser parser)
{
ParseUnusedType(parser, ref _type); // Parse the type from the Unused list
_modifiers = ModifiersHelpers.Parse(parser, this); // Parse any modifiers in reverse from the Unused list
ParseUnusedAnnotations(parser, this, false); // Parse attributes and/or doc comments from the Unused list
}
/// <summary>
/// Determine if the specified comment should be associated with the current code object during parsing.
/// </summary>
public override bool AssociateCommentWhenParsing(CommentBase comment)
{
return true;
}
#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 (_body != null && (flags & (ResolveFlags.Phase1 | ResolveFlags.Phase2)) == 0)
_body.Resolve(ResolveCategory.CodeObject, flags);
return this;
}
/// <summary>
/// Resolve child code objects that match the specified name, moving up the tree until a complete match is found.
/// </summary>
public override void ResolveRefUp(string name, Resolver resolver)
{
if (_body != null)
{
// Check any accessor parameters, so that 'value' can be resolved if referenced in doc comments
foreach (AccessorDeclWithValue accessorDeclWithValue in _body.Find<AccessorDeclWithValue>())
ChildListHelpers.ResolveRef(accessorDeclWithValue.Parameters, name, resolver);
_body.ResolveRef(name, resolver);
if (resolver.HasCompleteMatch) return; // Abort if we found a match
}
if (_parent != null)
_parent.ResolveRefUp(name, resolver);
}
/// <summary>
/// Returns true if the code object is an <see cref="UnresolvedRef"/> or has any <see cref="UnresolvedRef"/> children.
/// </summary>
public override bool HasUnresolvedRef()
{
if (_type != null && _type.HasUnresolvedRef())
return true;
if (_name is Expression && ((Expression)_name).HasUnresolvedRef())
return true;
return base.HasUnresolvedRef();
}
/// <summary>
/// Evaluate the type of the <see cref="PropertyDecl"/>.
/// </summary>
/// <remarks>This method evaluates the type expression into a <see cref="TypeRefBase"/>, which will properly evaluate the type arguments
/// of nested types. It also handles constants and the type being null.</remarks>
public TypeRefBase EvaluateType(bool withoutConstants)
{
return (_type != null ? _type.EvaluateType(withoutConstants) : null);
}
/// <summary>
/// Evaluate the type of the <see cref="PropertyDecl"/>.
/// </summary>
/// <remarks>This method evaluates the type expression into a <see cref="TypeRefBase"/>, which will properly evaluate the type arguments
/// of nested types. It also handles constants and the type being null.</remarks>
public TypeRefBase EvaluateType()
{
return (_type != null ? _type.EvaluateType(false) : null);
}
#endregion
#region /* ANALYSIS */
/// <summary>
/// Accept the specified visitor object for the current code object and it's children.
/// </summary>
public override void Accept(IVisitor visitor)
{
// Don't visit generated properties
if (!IsGenerated)
{
AcceptAnnotations(visitor);
visitor.Visit(this);
if (_type != null)
_type.Accept(visitor);
AcceptEOLComments(visitor);
AcceptAnnotations(visitor, AnnotationFlags.IsPostfix);
if (_body != null)
_body.Accept(visitor);
}
}
#endregion
#region /* FORMATTING */
/// <summary>
/// Determine a default of 1 or 2 newlines when adding items to a <see cref="Block"/>.
/// </summary>
public override int DefaultNewLines(CodeObject previous)
{
// Always default to a blank line before a property declaration, unless it's formatted on a
// single line and is preceeded by another single-line property declaration of the same type or a comment.
if (IsSingleLine && ((previous.GetType() == GetType() && previous.IsSingleLine) || previous is Comment))
return 1;
return 2;
}
/// <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;
}
}
/// <summary>
/// Reformat the <see cref="Block"/> body.
/// </summary>
public override void ReformatBlock()
{
base.ReformatBlock();
// If the child accessors have no bodies (they are interface properties/indexers/events),
// then format the entire statement as a single line.
if (_body != null)
{
if (Enumerable.All(_body, delegate(CodeObject codeObject) { return codeObject is AccessorDecl && ((AccessorDecl)codeObject).Body == null; }))
IsSingleLine = true;
}
}
/// <summary>
/// Determines if the code object only requires a single line for display.
/// </summary>
public override bool IsSingleLine
{
get
{
return (base.IsSingleLine && (_type == null || (!_type.IsFirstOnLine && _type.IsSingleLine))
&& (!(_name is Expression) || (!((Expression)_name).IsFirstOnLine && ((Expression)_name).IsSingleLine)));
}
set
{
base.IsSingleLine = value;
if (value)
{
if (_type != null)
{
_type.IsFirstOnLine = false;
_type.IsSingleLine = true;
}
if (_name is Expression)
{
((Expression)_name).IsFirstOnLine = false;
((Expression)_name).IsSingleLine = true;
}
}
}
}
#endregion
#region /* RENDERING */
protected override void AsTextPrefix(CodeWriter writer, RenderFlags flags)
{
ModifiersHelpers.AsText(_modifiers, writer);
}
protected override void AsTextStatement(CodeWriter writer, RenderFlags flags)
{
RenderFlags passFlags = (flags & RenderFlags.PassMask);
if (_type != null)
_type.AsText(writer, passFlags | RenderFlags.IsPrefix);
UpdateLineCol(writer, flags);
if (flags.HasFlag(RenderFlags.Description) && _parent is TypeDecl)
{
((TypeDecl)_parent).AsTextName(writer, flags);
Dot.AsTextDot(writer);
}
if (_name is string)
writer.WriteIdentifier((string)_name, flags);
else if (_name is Expression)
((Expression)_name).AsText(writer, passFlags & ~(RenderFlags.Description | RenderFlags.ShowParentTypes));
}
#endregion
}
}