// 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 the declaration of a parameter to a method.
/// </summary>
/// <remarks>
/// A ParameterDecl can optionally have a modifier of:
/// ref, out, params (last parameter only)
/// Parameters don't support compound (multi) declarations like other VariableDecls.
/// </remarks>
public class ParameterDecl : VariableDecl
{
#region /* FIELDS */
protected ParameterModifier _modifier;
#endregion
#region /* CONSTRUCTORS */
/// <summary>
/// Create a <see cref="ParameterDecl"/>.
/// </summary>
/// <param name="name">The name of the parameter.</param>
/// <param name="type">The type of the parameter</param>
/// <param name="defaultValue">The default value expression for the parameter.</param>
public ParameterDecl(string name, Expression type, Expression defaultValue)
: base(name, type, defaultValue)
{ }
/// <summary>
/// Create a <see cref="ParameterDecl"/>.
/// </summary>
/// <param name="name">The name of the parameter.</param>
/// <param name="type">The type of the parameter</param>
public ParameterDecl(string name, Expression type)
: base(name, type, null)
{ }
/// <summary>
/// Create a <see cref="ParameterDecl"/>.
/// </summary>
/// <param name="name">The name of the parameter.</param>
/// <param name="type">The type of the parameter</param>
/// <param name="modifier">The ParameterModifier for the parameter.</param>
/// <param name="defaultValue">The default value expression for the parameter.</param>
public ParameterDecl(string name, Expression type, ParameterModifier modifier, Expression defaultValue)
: base(name, type, defaultValue)
{
_modifier = modifier;
}
/// <summary>
/// Create a <see cref="ParameterDecl"/>.
/// </summary>
/// <param name="name">The name of the parameter.</param>
/// <param name="type">The type of the parameter</param>
/// <param name="modifier">The ParameterModifier for the parameter.</param>
public ParameterDecl(string name, Expression type, ParameterModifier modifier)
: base(name, type, null)
{
_modifier = modifier;
}
#endregion
#region /* PROPERTIES */
/// <summary>
/// The descriptive category of the code object.
/// </summary>
public override string Category
{
get { return "parameter"; }
}
/// <summary>
/// The <see cref="ParameterModifier"/> for the parameter, if any.
/// </summary>
public ParameterModifier Modifier
{
get { return _modifier; }
}
/// <summary>
/// Determines if the parameter is a 'params' parameter.
/// </summary>
public bool IsParams
{
get { return (_modifier == ParameterModifier.Params); }
set { _modifier = (value ? ParameterModifier.Params : (IsParams ? ParameterModifier.None : _modifier)); }
}
/// <summary>
/// Determines if the parameter is being passed by reference.
/// </summary>
public bool IsRef
{
get { return (_modifier == ParameterModifier.Ref); }
set { _modifier = (value ? ParameterModifier.Ref : (IsRef ? ParameterModifier.None : _modifier)); }
}
/// <summary>
/// Determines if the parameter is being passed out only.
/// </summary>
public bool IsOut
{
get { return (_modifier == ParameterModifier.Out); }
set { _modifier = (value ? ParameterModifier.Out : (IsOut ? ParameterModifier.None : _modifier)); }
}
/// <summary>
/// Always <c>false</c> for a parameter.
/// </summary>
public override bool IsConst
{
get { return false; }
set { }
}
/// <summary>
/// Always <c>false</c> for a parameter.
/// </summary>
public override bool IsStatic
{
get { return false; }
set { }
}
#endregion
#region /* METHODS */
/// <summary>
/// Attach an <see cref="Annotation"/> (<see cref="Comment"/>, <see cref="DocComment"/>, <see cref="Attribute"/>, <see cref="CompilerDirective"/>, or <see cref="Message"/>) to the <see cref="CodeObject"/>.
/// </summary>
/// <param name="annotation">The <see cref="Annotation"/>.</param>
/// <param name="atFront">Inserts at the front if true, otherwise adds at the end.</param>
public override void AttachAnnotation(Annotation annotation, bool atFront)
{
// Force attached annotations to same-line for parameters
annotation.IsFirstOnLine = false;
base.AttachAnnotation(annotation, atFront);
}
/// <summary>
/// Create a reference to the <see cref="ParameterDecl"/>.
/// </summary>
/// <param name="isFirstOnLine">True if the reference should be displayed on a new line.</param>
/// <returns>A <see cref="ParameterRef"/>.</returns>
public override SymbolicRef CreateRef(bool isFirstOnLine)
{
return new ParameterRef(this, isFirstOnLine);
}
#endregion
#region /* PARSING */
/// <summary>
/// The token used to parse the start of a parameter list.
/// </summary>
public const string ParseTokenStart = Expression.ParseTokenStartGroup;
/// <summary>
/// The token used to parse the end of a parameter list.
/// </summary>
public const string ParseTokenEnd = Expression.ParseTokenEndGroup;
/// <summary>
/// The token used to parse between parameters.
/// </summary>
public const string ParseTokenSeparator = Expression.ParseTokenSeparator;
/// <summary>
/// The token used to parse 'ref' parameters.
/// </summary>
public const string ParseTokenRef = "ref";
/// <summary>
/// The token used to parse 'out' parameters.
/// </summary>
public const string ParseTokenOut = "out";
/// <summary>
/// The token used to parse 'params' parameters.
/// </summary>
public const string ParseTokenParams = "params";
/// <summary>
/// The token used to parse 'this' parameters.
/// </summary>
public const string ParseTokenThis = "this";
protected ParameterDecl(Parser parser, CodeObject parent)
: base(parser, parent)
{ }
protected ParameterDecl(Parser parser, CodeObject parent, string parseTokenEnd)
: base(parser, parent)
{
// Get any comments after the '('
if (parser.LastToken.Text == ParseTokenStart)
MoveAllComments(parser.LastToken);
// If we're starting with an attribute, ignore any newline parsed in the base constructor
if (parser.TokenText == Attribute.ParseTokenStart)
IsFirstOnLine = false;
Attribute.ParseAttributes(parser, this); // Parse any attributes
// Parse the optional modifier
_modifier = ParseParameterModifier(parser.TokenText);
if (_modifier != ParameterModifier.None)
{
parent.MoveFormatting(parser.Token);
parser.NextToken(); // Move past the modifier
}
ParseType(parser); // Parse the type
ParseName(parser, parseTokenEnd);
ParseInitialization(parser, parent); // Parse the initialization (if any)
MoveEOLComment(parser.LastToken); // Associate any skipped EOL comment
}
/// <summary>
/// Parse a list of parameters.
/// </summary>
public static ChildList<ParameterDecl> ParseList(Parser parser, CodeObject parent, string parseTokenStart,
string parseTokenEnd, bool forceEmpty, out bool isEndFirstOnLine)
{
ChildList<ParameterDecl> parameterDecls = null;
isEndFirstOnLine = false;
if (parser.TokenText == parseTokenStart)
{
Token lastToken = parser.Token;
parser.NextToken(); // Move past '(' or '['
// Force an empty collection (vs null) if the flag is set
if (forceEmpty)
parameterDecls = new ChildList<ParameterDecl>(parent);
// Create a string of possible terminators (assuming 1 char terminators for now)
string terminators = parseTokenEnd + ParseTokenTerminator + Block.ParseTokenStart + Block.ParseTokenEnd + Index.ParseTokenEnd;
while (parser.TokenText != null && (parser.TokenText.Length != 1 || terminators.IndexOf(parser.TokenText[0]) < 0))
{
ParameterDecl parameterDecl = new ParameterDecl(parser, parent, parseTokenEnd);
// Move any preceeding comments to the current ParameterDecl
parameterDecl.MoveComments(lastToken);
if (parameterDecls == null)
parameterDecls = new ChildList<ParameterDecl>(parent);
parameterDecls.Add(parameterDecl);
lastToken = parser.Token;
if (parser.TokenText == ParseTokenSeparator)
{
parser.NextToken(); // Move past ','
// Associate any EOL comment on the ',' to the last ParameterDecl
parameterDecl.MoveEOLComment(lastToken, false, false);
// Move any remaining regular comments as Post comments, if on a line by themselves
if (parser.Token.IsFirstOnLine)
parameterDecl.MoveCommentsAsPost(lastToken);
}
}
if (parent.ParseExpectedToken(parser, parseTokenEnd)) // Move past ')' or ']'
{
isEndFirstOnLine = parser.LastToken.IsFirstOnLine;
if (parameterDecls == null || parameterDecls.Count == 0)
parent.MoveAllComments(lastToken, false, false, AnnotationFlags.IsInfix1);
parent.MoveEOLComment(parser.LastToken); // Associate any skipped EOL comment with the parent
}
}
return parameterDecls;
}
protected static ParameterModifier ParseParameterModifier(string modifierName)
{
ParameterModifier modifier;
switch (modifierName)
{
case ParseTokenRef: modifier = ParameterModifier.Ref; break;
case ParseTokenOut: modifier = ParameterModifier.Out; break;
case ParseTokenParams: modifier = ParameterModifier.Params; break;
case ParseTokenThis: modifier = ParameterModifier.This; break;
default: modifier = ParameterModifier.None; break;
}
return modifier;
}
#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.Phase2)) == 0)
ResolveAttributes(flags);
return base.Resolve(ResolveCategory.CodeObject, flags);
}
/// <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)
{
// Use a special call to avoid wasting time searching method bodies and parameters for the
// type of the parameter (assumes our parent is a MethodDeclBase or an AnonymousMethod,
// with the skip method just moving up to the next level without doing any resolving - a
// GenericMethodDecl overrides the skip method to check type parameters).
if (_parent != null)
_parent.ResolveRefUpSkipMethodBody(name, resolver);
}
/// <summary>
/// Get the constant value of the variable (if any).
/// </summary>
/// <returns>An object of the type of the variable with a value of the constant used
/// to initialize it (if any), or null if the variable isn't a constant.</returns>
public override object GetValue()
{
// Always null for parameters, since they have no true initializer (just a default value), and
// we don't yet perform runtime value tracing.
return null;
}
#endregion
#region /* FORMATTING */
/// <summary>
/// True if the code object defaults to starting on a new line.
/// </summary>
public override bool IsFirstOnLineDefault
{
get { return false; }
}
/// <summary>
/// True if the <see cref="Statement"/> has a terminator character by default.
/// </summary>
public override bool HasTerminatorDefault
{
get { return false; }
}
/// <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 zero, also change all prefix attributes to zero
bool isFirstOnLine = (value != 0);
if (_annotations != null && !isFirstOnLine && IsFirstOnLine)
{
foreach (Annotation annotation in _annotations)
{
if (annotation is Attribute)
annotation.IsFirstOnLine = false;
}
}
base.NewLines = value;
}
}
#endregion
#region /* RENDERING */
/// <summary>
/// Format a <see cref="ParameterModifier"/> as a string.
/// </summary>
public static string ParameterModifierToString(ParameterModifier modifier)
{
switch (modifier)
{
case ParameterModifier.Ref: return ParseTokenRef;
case ParameterModifier.Out: return ParseTokenOut;
case ParameterModifier.Params: return ParseTokenParams;
case ParameterModifier.This: return ParseTokenThis;
}
return "";
}
protected override void AsTextStatement(CodeWriter writer, RenderFlags flags)
{
if (_modifier != ParameterModifier.None)
writer.Write(ParameterModifierToString(_modifier) + " ");
AsTextType(writer, flags);
UpdateLineCol(writer, flags);
writer.WriteIdentifier(_name, flags);
}
#endregion
}
}