// 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 System.Collections;
using System.Collections.Generic;
using Nova.Parsing;
using Nova.Rendering;
namespace Nova.CodeDOM
{
/// <summary>
/// Declares a type that represents a reference to a method.
/// </summary>
/// <remarks>
/// A delegate has no visible body, but one is generated by the compiler to hold the
/// compiler-generated BeginInvoke and EndInvoke methods.
/// </remarks>
public class DelegateDecl : TypeDecl, IParameters
{
#region /* FIELDS */
/// <summary>
/// The return type is an <see cref="Expression"/> that must evaluate to a <see cref="TypeRef"/> in valid code.
/// </summary>
protected Expression _returnType;
protected ChildList<ParameterDecl> _parameters;
#endregion
#region /* CONSTRUCTORS */
/// <summary>
/// Create a <see cref="DelegateDecl"/> with the specified name and return type.
/// </summary>
public DelegateDecl(string name, Expression returnType, Modifiers modifiers)
: base(name, modifiers, new Block { IsGenerated = true })
{
ReturnType = returnType;
GenerateMethods(); // Generate invoke methods and constructor
}
/// <summary>
/// Create a <see cref="DelegateDecl"/> with the specified name and return type.
/// </summary>
public DelegateDecl(string name, Expression returnType)
: this(name, returnType, Modifiers.None)
{ }
/// <summary>
/// Create a <see cref="DelegateDecl"/> with the specified name, return type, modifiers, and parameters.
/// </summary>
public DelegateDecl(string name, Expression returnType, Modifiers modifiers, params ParameterDecl[] parameters)
: this(name, returnType, modifiers)
{
CreateParameters().AddRange(parameters);
}
/// <summary>
/// Create a <see cref="DelegateDecl"/> with the specified name, return type, modifiers, and type parameters.
/// </summary>
public DelegateDecl(string name, Expression returnType, Modifiers modifiers, params TypeParameter[] typeParameters)
: this(name, returnType, modifiers)
{
CreateTypeParameters().AddRange(typeParameters);
}
/// <summary>
/// Create a <see cref="DelegateDecl"/> with the specified name and return type.
/// </summary>
public DelegateDecl(string name, Type returnType, Modifiers modifiers)
: this(name, TypeRef.Create(returnType), modifiers)
{ }
/// <summary>
/// Create a <see cref="DelegateDecl"/> with the specified name and return type.
/// </summary>
public DelegateDecl(string name, Type returnType)
: this(name, TypeRef.Create(returnType), Modifiers.None)
{ }
/// <summary>
/// Create a <see cref="DelegateDecl"/> with the specified name, return type, modifiers, and parameters.
/// </summary>
public DelegateDecl(string name, Type returnType, Modifiers modifiers, params ParameterDecl[] parameters)
: this(name, TypeRef.Create(returnType), modifiers, parameters)
{ }
/// <summary>
/// Create a <see cref="DelegateDecl"/> with the specified name, return type, modifiers, and type parameters.
/// </summary>
public DelegateDecl(string name, Type returnType, Modifiers modifiers, params TypeParameter[] typeParameters)
: this(name, TypeRef.Create(returnType), modifiers, typeParameters)
{ }
/// <summary>
/// Create a <see cref="DelegateDecl"/> with the specified name and return type.
/// </summary>
public DelegateDecl(string name, ITypeDecl returnType, Modifiers modifiers)
: this(name, returnType.CreateRef(), modifiers)
{ }
/// <summary>
/// Create a <see cref="DelegateDecl"/> with the specified name and return type.
/// </summary>
public DelegateDecl(string name, ITypeDecl returnType)
: this(name, returnType.CreateRef(), Modifiers.None)
{ }
/// <summary>
/// Create a <see cref="DelegateDecl"/> with the specified name, return type, modifiers, and parameters.
/// </summary>
public DelegateDecl(string name, ITypeDecl returnType, Modifiers modifiers, params ParameterDecl[] parameters)
: this(name, returnType.CreateRef(), modifiers, parameters)
{ }
/// <summary>
/// Create a <see cref="DelegateDecl"/> with the specified name, return type, modifiers, and type parameters.
/// </summary>
public DelegateDecl(string name, ITypeDecl returnType, Modifiers modifiers, params TypeParameter[] typeParameters)
: this(name, returnType.CreateRef(), modifiers, typeParameters)
{ }
#endregion
#region /* PROPERTIES */
/// <summary>
/// The return type of the delegate (never null - will be type 'void' instead).
/// </summary>
public Expression ReturnType
{
get { return (_returnType ?? TypeRef.VoidRef); }
set { SetField(ref _returnType, value, true); }
}
/// <summary>
/// The list of <see cref="ParameterDecl"/>s.
/// </summary>
public ChildList<ParameterDecl> Parameters
{
get { return _parameters; }
}
/// <summary>
/// True if there are any parameters.
/// </summary>
public bool HasParameters
{
get { return (_parameters != null && _parameters.Count > 0); }
}
/// <summary>
/// The number of parameters.
/// </summary>
public int ParameterCount
{
get { return (_parameters != null ? _parameters.Count : 0); }
}
/// <summary>
/// True if the type is a delegate type.
/// </summary>
public override bool IsDelegateType
{
get { return true; }
}
/// <summary>
/// The keyword associated with the <see cref="Statement"/>.
/// </summary>
public override string Keyword
{
get { return ParseToken; }
}
#endregion
#region /* METHODS */
/// <summary>
/// Create the list of <see cref="ParameterDecl"/>s, or return the existing list.
/// </summary>
public ChildList<ParameterDecl> CreateParameters()
{
if (_parameters == null)
_parameters = new ChildList<ParameterDecl>(this);
return _parameters;
}
/// <summary>
/// Add one or more <see cref="ParameterDecl"/>s.
/// </summary>
public void AddParameter(ParameterDecl parameterDecl)
{
CreateParameters().Add(parameterDecl);
}
/// <summary>
/// Get the parameters of this delegate type.
/// </summary>
public override ICollection GetDelegateParameters()
{
// Return an empty list instead of null if we have no parameters - this is used elsewhere
// to distinguish between zero parameters and no parameters available.
return (_parameters ?? new List<ParameterDecl>());
}
/// <summary>
/// Get the return type of this delegate type.
/// </summary>
public override TypeRefBase GetDelegateReturnType()
{
return (_returnType != null ? _returnType.SkipPrefixes() as TypeRefBase : null);
}
/// <summary>
/// Get the base type.
/// </summary>
public override TypeRef GetBaseType()
{
return TypeRef.MulticastDelegateRef;
}
/// <summary>
/// Get the non-static constructor with the specified parameters.
/// </summary>
public override ConstructorRef GetConstructor(params TypeRefBase[] parameterTypes)
{
ConstructorDecl found = GetMethod<ConstructorDecl>(Name, parameterTypes);
if (found != null)
return (ConstructorRef)found.CreateRef();
return null; // Don't look in base types for DelegateDecls
}
/// <summary>
/// The name of the parameter of the constructor of the delegate that accepts a delegate type.
/// </summary>
public const string DelegateConstructorParameterName = "target";
/// <summary>
/// Get all non-static constructors for this type.
/// </summary>
public override NamedCodeObjectGroup GetConstructors(bool currentPartOnly)
{
// Find the constructor (ignore 'currentPartOnly', since delegates have no bodies)
TypeRef delegateTypeRef = CreateRef();
ConstructorRef constructorRef = GetConstructor(delegateTypeRef);
return new NamedCodeObjectGroup(constructorRef != null ? constructorRef.Reference : null);
}
/// <summary>
/// Create (or re-create) the compiler-generated invoke methods and constructor.
/// This method should be called whenever the parameters of the delegate are set or changed.
/// </summary>
public void GenerateMethods()
{
// Remove any existing methods before generating them - since the Body is compiler-generated just
// to hold these methods, we can just re-create it.
Body = new Block { IsGenerated = true };
// The Invoke method has the same parameters as the delegate, and the same return type
MethodDecl invokeDecl = new MethodDecl("Invoke", (Expression)_returnType.Clone(), Modifiers.Public) { IsGenerated = true };
invokeDecl.Parameters = (_parameters != null ? ChildListHelpers.Clone(_parameters, invokeDecl) : null);
Add(invokeDecl);
// The BeginInvoke method has the same parameters as the delegate, plus 2 extra ones, and a return type of IAsyncResult
MethodDecl beginInvokeDecl = new MethodDecl("BeginInvoke", (TypeRef)TypeRef.IAsyncResultRef.Clone(), Modifiers.Public) { IsGenerated = true };
ChildList<ParameterDecl> parameters = (_parameters != null ? ChildListHelpers.Clone(_parameters, beginInvokeDecl) : new ChildList<ParameterDecl>(beginInvokeDecl));
parameters.Add(new ParameterDecl("callback", (TypeRef)TypeRef.AsyncCallbackRef.Clone()));
parameters.Add(new ParameterDecl("object", (TypeRef)TypeRef.ObjectRef.Clone()));
beginInvokeDecl.Parameters = parameters;
Add(beginInvokeDecl);
// The EndInvoke method has any 'ref' or 'out' parameters of the delegate, plus 1 extra one, and the same return type
MethodDecl endInvokeDecl = new MethodDecl("EndInvoke", (Expression)_returnType.Clone(), Modifiers.Public) { IsGenerated = true };
parameters = new ChildList<ParameterDecl>(endInvokeDecl);
if (_parameters != null)
{
foreach (ParameterDecl parameterDecl in _parameters)
{
if (parameterDecl.IsRef || parameterDecl.IsOut)
parameters.Add((ParameterDecl)parameterDecl.Clone());
}
}
parameters.Add(new ParameterDecl("result", (TypeRef)TypeRef.IAsyncResultRef.Clone()));
endInvokeDecl.Parameters = parameters;
Add(endInvokeDecl);
// Delegates have a constructor that takes an object and an IntPtr that is used internally by the compiler during
// code generation. We have to create a dummy constructor that will allow a MethodRef to be passed to it, in order
// to make the C# syntax work when resolving.
TypeRef delegateTypeRef = CreateRef();
ConstructorDecl constructor = new ConstructorDecl(new[] { new ParameterDecl(DelegateConstructorParameterName, delegateTypeRef) }) { IsGenerated = true };
Add(constructor);
}
/// <summary>
/// Deep-clone the code object.
/// </summary>
public override CodeObject Clone()
{
DelegateDecl clone = (DelegateDecl)base.Clone();
clone.CloneField(ref clone._returnType, _returnType);
clone._typeParameters = ChildListHelpers.Clone(_typeParameters, clone);
return clone;
}
#endregion
#region /* PARSING */
/// <summary>
/// The token used to parse the code object.
/// </summary>
public const string ParseToken = "delegate";
/// <summary>
/// The token used to parse the start of the parameters.
/// </summary>
public const string ParseTokenStart = ParameterDecl.ParseTokenStart;
/// <summary>
/// The token used to parse the end of the parameters.
/// </summary>
public const string ParseTokenEnd = ParameterDecl.ParseTokenEnd;
internal static void AddParsePoints()
{
// Delegates are only valid with a Namespace or 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 (AnonymousMethod uses 100)
Parser.AddParsePoint(ParseToken, Parse, typeof(IBlock));
}
/// <summary>
/// Parse a <see cref="DelegateDecl"/>.
/// </summary>
public static DelegateDecl Parse(Parser parser, CodeObject parent, ParseFlags flags)
{
return new DelegateDecl(parser, parent);
}
protected DelegateDecl(Parser parser, CodeObject parent)
: base(parser, parent)
{
MoveComments(parser.LastToken); // Get any comments before 'class'
parser.NextToken(); // Move past 'delegate'
SetField(ref _returnType, Expression.Parse(parser, this, true), true); // Parse the return type
ParseNameTypeParameters(parser); // Parse the name and any optional type parameters
// Parse the parameter declarations
bool isEndFirstOnLine;
_parameters = ParameterDecl.ParseList(parser, this, ParseTokenStart, ParseTokenEnd, false, out isEndFirstOnLine);
IsEndFirstOnLine = isEndFirstOnLine;
ParseModifiersAndAnnotations(parser); // Parse any attributes and/or modifiers
ParseConstraintClauses(parser); // Parse any constraint clauses
ParseTerminator(parser);
GenerateMethods(); // Generate invoke methods and constructor
}
#endregion
#region /* FORMATTING */
/// <summary>
/// True if the <see cref="Statement"/> has parens around its argument.
/// </summary>
public override bool HasArgumentParens
{
get { return true; }
}
/// <summary>
/// True if the <see cref="Statement"/> has a terminator character by default.
/// </summary>
public override bool HasTerminatorDefault
{
get { return true; }
}
/// <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 delegate declaration.
if (HasFirstOnLineAnnotations || !(previous is DelegateDecl))
return 2;
return 1;
}
/// <summary>
/// Determines if the code object only requires a single line for display.
/// </summary>
public override bool IsSingleLine
{
get
{
return (base.IsSingleLine && (_returnType == null || (!_returnType.IsFirstOnLine && _returnType.IsSingleLine))
&& (_parameters == null || _parameters.Count == 0 || (!_parameters[0].IsFirstOnLine && _parameters.IsSingleLine)));
}
set
{
base.IsSingleLine = value;
if (value)
{
if (_returnType != null)
{
_returnType.IsFirstOnLine = false;
_returnType.IsSingleLine = true;
}
if (_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);
UpdateLineCol(writer, flags);
writer.Write(ParseToken);
_returnType.AsText(writer, passFlags | RenderFlags.IsPrefix | RenderFlags.PrefixSpace);
base.AsTextArgument(writer, flags);
}
protected override void AsTextArgumentPrefix(CodeWriter writer, RenderFlags flags)
{ }
protected override void AsTextArgument(CodeWriter writer, RenderFlags flags)
{
RenderFlags passFlags = (flags & RenderFlags.PassMask);
AsTextInfixComments(writer, 0, flags);
writer.WriteList(_parameters, passFlags, this);
if (IsEndFirstOnLine)
writer.WriteLine();
}
#endregion
}
}