// 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 Nova.Parsing;
using Nova.Rendering;
using Nova.Resolving;
namespace Nova.CodeDOM
{
/// <summary>
/// Represents an "indexer" - an indexed property.
/// </summary>
/// <remarks>
/// IndexerDecls are always represented in C# source with a name of 'this', but default to an internal name of 'Item'.
/// The internal name can actually be overridden with the IndexerName attribute (to prevent name collisions, or
/// for no particular reason - the indexer for System.String is named 'Chars'), and therefore indexers are resolved
/// by looking for indexers regardless of their names. The C# compiler pretends indexers don't exist if you try to
/// reference them directly by name, although VS 2010 intellisense will show their parameter lists.
/// </remarks>
public class IndexerDecl : PropertyDeclBase, IParameters
{
#region /* CONSTANTS */
/// <summary>
/// The internal name for an indexer.
/// </summary>
public const string IndexerName = "Item";
#endregion
#region /* FIELDS */
// The '_name' base-class member should always be an Expression - which should be either a ThisRef,
// or a Dot operator with a TypeRef to an Interface on the left and a ThisRef on the right (ThisRef
// is used by default to represent the 'this', even though it's really an IndexerDecl and not a
// self-reference).
protected ChildList<ParameterDecl> _parameters;
#endregion
#region /* CONSTRUCTORS */
/// <summary>
/// Create an <see cref="IndexerDecl"/>.
/// </summary>
public IndexerDecl(Expression name, Expression type, Modifiers modifiers, params ParameterDecl[] parameters)
: base(CheckUnresolvedThisRef(name), type, modifiers)
{
// Save any parameters - these are NOT stored on the SetterDecl/GetterDecl, but are
// instead accessed through the parent.
_parameters = new ChildList<ParameterDecl>(parameters, this);
}
/// <summary>
/// Create an <see cref="IndexerDecl"/>.
/// </summary>
public IndexerDecl(Expression name, Expression type, params ParameterDecl[] parameters)
: this(name, type, Modifiers.None, parameters)
{ }
/// <summary>
/// Create an <see cref="IndexerDecl"/>.
/// </summary>
public IndexerDecl(Expression type, Modifiers modifiers, params ParameterDecl[] parameters)
: this(new ThisRef(), type, modifiers, parameters)
{ }
/// <summary>
/// Create an <see cref="IndexerDecl"/>.
/// </summary>
public IndexerDecl(Expression type, params ParameterDecl[] parameters)
: this(type, Modifiers.None, parameters)
{ }
#endregion
#region /* PROPERTIES */
/// <summary>
/// The name of the <see cref="IndexerDecl"/>.
/// </summary>
public override string Name
{
get
{
// The internal name of an Indexer is always 'Item', even though it's always displayed
// as 'this' in the GUI or text rendering (it's actually 'this.Item').
if (_name is ThisRef)
return IndexerName;
// If it's an explicit interface implementation, use the full name
if (_name is Expression)
return ((Expression)_name).AsString();
return null;
}
}
/// <summary>
/// The <see cref="ThisRef"/> or <see cref="Dot"/> expression representing the name of the <see cref="IndexerDecl"/>.
/// </summary>
public Expression NameExpression
{
get { return (_name as Expression); }
}
/// <summary>
/// The descriptive category of the code object.
/// </summary>
public override string Category
{
get { return "indexer"; }
}
/// <summary>
/// A collection of <see cref="ParameterDecl"/>s for the parameters of the indexer.
/// </summary>
public ChildList<ParameterDecl> Parameters
{
get { return _parameters; }
}
/// <summary>
/// True if the indexer has parameters.
/// </summary>
public bool HasParameters
{
get { return (_parameters != null && _parameters.Count > 0); }
}
/// <summary>
/// The number of parameters the indexer has.
/// </summary>
public int ParameterCount
{
get { return (_parameters != null ? _parameters.Count : 0); }
}
/// <summary>
/// True if the indexer has a getter method.
/// </summary>
public bool HasGetter
{
get { return (_body.FindFirst<GetterDecl>() != null); }
}
/// <summary>
/// True if the indexer has a setter method.
/// </summary>
public bool HasSetter
{
get { return (_body.FindFirst<SetterDecl>() != null); }
}
/// <summary>
/// The 'getter' method for the indexer.
/// </summary>
public GetterDecl Getter
{
get { return _body.FindFirst<GetterDecl>(); }
set
{
if (_body != null)
{
GetterDecl existing = _body.FindFirst<GetterDecl>();
if (existing != null)
_body.Remove(existing);
}
Insert(0, value); // Always put the 'getter' first
}
}
/// <summary>
/// The 'setter' method for the indexer.
/// </summary>
public SetterDecl Setter
{
get { return _body.FindFirst<SetterDecl>(); }
set
{
if (_body != null)
{
SetterDecl existing = _body.FindFirst<SetterDecl>();
if (existing != null)
_body.Remove(existing);
}
Insert((_body != null ? _body.Count : 0), value); // Always put the 'setter' after any 'getter'
}
}
/// <summary>
/// True if the indexer is readable.
/// </summary>
public override bool IsReadable { get { return HasGetter; } }
/// <summary>
/// True if the indexer is writable.
/// </summary>
public override bool IsWritable { get { return HasSetter; } }
#endregion
#region /* METHODS */
/// <summary>
/// Create a reference to the <see cref="IndexerDecl"/>.
/// </summary>
/// <param name="isFirstOnLine">True if the reference should be displayed on a new line.</param>
/// <returns>An <see cref="IndexerRef"/>.</returns>
public override SymbolicRef CreateRef(bool isFirstOnLine)
{
return new IndexerRef(this, isFirstOnLine);
}
private static Expression CheckUnresolvedThisRef(Expression expression)
{
// If we have an "Interface.this" expression, convert a ThisRef to an UnresolvedThisRef
if (expression is Dot)
{
Dot dot = (Dot)expression;
if (dot.Right is ThisRef)
dot.Right = new UnresolvedThisRef(dot.Right as ThisRef);
}
return expression;
}
/// <summary>
/// Get the IsPrivate access right for the specified usage, and if not private then also get the IsProtected and IsInternal rights.
/// </summary>
/// <param name="isTargetOfAssignment">Usage - true if the target of an assignment ('lvalue'), otherwise false.</param>
/// <param name="isPrivate">True if the access is private.</param>
/// <param name="isProtected">True if the access is protected.</param>
/// <param name="isInternal">True if the access is internal.</param>
public override void GetAccessRights(bool isTargetOfAssignment, out bool isPrivate, out bool isProtected, out bool isInternal)
{
isPrivate = isProtected = isInternal = false;
// The access rights of an indexer actually depend on the rights of the corresponding
// getter/setter, depending upon whether we're assigning to it or not.
if (isTargetOfAssignment)
{
SetterDecl setterDecl = Setter;
if (setterDecl != null)
{
isPrivate = setterDecl.IsPrivate;
if (!isPrivate)
{
isProtected = setterDecl.IsProtected;
isInternal = setterDecl.IsInternal;
}
}
}
else
{
GetterDecl getterDecl = Getter;
if (getterDecl != null)
{
isPrivate = getterDecl.IsPrivate;
if (!isPrivate)
{
isProtected = getterDecl.IsProtected;
isInternal = getterDecl.IsInternal;
}
}
}
}
/// <summary>
/// Deep-clone the code object.
/// </summary>
public override CodeObject Clone()
{
IndexerDecl clone = (IndexerDecl)base.Clone();
clone._parameters = ChildListHelpers.Clone(_parameters, clone);
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 override string GetFullName(bool descriptive)
{
string name;
if (_name is Expression)
name = ((Expression)_name).AsString();
else
name = ThisRef.ParseToken;
if (descriptive)
name += MethodDeclBase.GetParametersAsString(ParseTokenStart, ParseTokenEnd, _parameters);
if (_parent is TypeDecl)
name = ((TypeDecl)_parent).GetFullName(descriptive) + "." + name;
return name;
}
#endregion
#region /* PARSING */
/// <summary>
/// The token used to parse the code object.
/// </summary>
public const string ParseToken = ThisRef.ParseToken;
/// <summary>
/// The token used to parse the start of the parameters.
/// </summary>
public const string ParseTokenStart = TypeRefBase.ParseTokenArrayStart;
/// <summary>
/// The token used to parse the end of the parameters.
/// </summary>
public const string ParseTokenEnd = TypeRefBase.ParseTokenArrayEnd;
internal static new void AddParsePoints()
{
// Indexer 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 0 (TypeRef uses 100, Index uses 200, Attribute uses 300)
Parser.AddParsePoint(ParseTokenStart, Parse, typeof(IBlock));
}
/// <summary>
/// Parse an <see cref="IndexerDecl"/>.
/// </summary>
public static new IndexerDecl 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)
{
// Verify that we have a 'this' or 'Interface.this' (in the first case, the 'this' will be
// a ThisRef, in the second case, it will be an UnresolvedThisRef).
CodeObject lastUnusedCodeObject = parser.LastUnusedCodeObject;
if (lastUnusedCodeObject is ThisRef || (lastUnusedCodeObject is Dot && ((Dot)lastUnusedCodeObject).Right is UnresolvedThisRef))
return new IndexerDecl(parser, parent);
}
return null;
}
protected IndexerDecl(Parser parser, CodeObject parent)
: base(parser, parent, false)
{
// Get the ThisRef or Dot expression. If it's a Dot, replace the ThisRef with an UnresolvedThisRef,
// which has an internal name of "Item", but displays as "this".
Expression expression = parser.RemoveLastUnusedExpression();
SetField(ref _name, CheckUnresolvedThisRef(expression), false);
Expression leftExpression = (expression is BinaryOperator ? ((BinaryOperator)expression).Left : expression);
_lineNumber = leftExpression.LineNumber;
_columnNumber = (ushort)leftExpression.ColumnNumber;
ParseTypeModifiersAnnotations(parser); // Parse type and any modifiers and/or attributes
// Parse the parameter declarations
bool isEndFirstOnLine;
_parameters = ParameterDecl.ParseList(parser, this, ParseTokenStart, ParseTokenEnd, false, out isEndFirstOnLine);
IsEndFirstOnLine = isEndFirstOnLine;
new Block(out _body, parser, this, true); // Parse the body
}
#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)
{
if (_type != null)
_type = (Expression)_type.Resolve(ResolveCategory.Type, flags);
ChildListHelpers.Resolve(_parameters, ResolveCategory.CodeObject, flags);
if (_name is Expression)
_name = ((Expression)_name).Resolve(ResolveCategory.Indexer, flags);
}
if ((flags & (ResolveFlags.Phase1 | ResolveFlags.Phase2)) == 0)
{
ResolveAttributes(flags);
if (_body != null)
_body.Resolve(ResolveCategory.CodeObject, flags);
ResolveDocComments(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 (resolver.ResolveCategory == ResolveCategory.Parameter)
ChildListHelpers.ResolveRef(_parameters, name, resolver);
else
{
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
}
ChildListHelpers.ResolveRef(_parameters, name, resolver);
if (_parent != null && !resolver.HasCompleteMatch)
_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 (ChildListHelpers.HasUnresolvedRef(_parameters))
return true;
return base.HasUnresolvedRef();
}
#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)
{
AcceptAnnotations(visitor);
visitor.Visit(this);
if (_type != null)
_type.Accept(visitor);
ChildListHelpers.Accept(_parameters, visitor);
AcceptEOLComments(visitor);
AcceptAnnotations(visitor, AnnotationFlags.IsPostfix);
if (_body != null)
_body.Accept(visitor);
}
#endregion
#region /* FORMATTING */
/// <summary>
/// True if the <see cref="Statement"/> has an argument.
/// </summary>
public override bool HasArgument
{
get { return true; }
}
/// <summary>
/// True if the <see cref="Statement"/> has parens around its argument.
/// </summary>
public override bool HasArgumentParens
{
get { return false; }
}
/// <summary>
/// Determines if the code object only requires a single line for display.
/// </summary>
public override bool IsSingleLine
{
get { return (base.IsSingleLine && (_parameters == null || _parameters.Count == 0 || (!_parameters[0].IsFirstOnLine && _parameters.IsSingleLine))); }
set
{
base.IsSingleLine = value;
if (value && _parameters != null && _parameters.Count > 0)
{
_parameters[0].IsFirstOnLine = false;
_parameters.IsSingleLine = true;
}
}
}
#endregion
#region /* RENDERING */
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 ThisRef)
((ThisRef)_name).AsText(writer, passFlags);
else if (_name is Expression)
((Expression)_name).AsText(writer, passFlags & ~(RenderFlags.Description | RenderFlags.ShowParentTypes));
}
protected override void AsTextArgumentPrefix(CodeWriter writer, RenderFlags flags)
{ }
protected override void AsTextArgument(CodeWriter writer, RenderFlags flags)
{
RenderFlags passFlags = (flags & RenderFlags.PassMask);
writer.Write(ParseTokenStart);
AsTextInfixComments(writer, 0, flags);
writer.WriteList(_parameters, passFlags, this);
if (IsEndFirstOnLine)
writer.WriteLine();
writer.Write(ParseTokenEnd);
}
#endregion
}
}