// 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;
using Nova.Resolving;
namespace Nova.CodeDOM
{
/// <summary>
/// Represents a 'for' loop.
/// </summary>
/// <remarks>
/// Has 3 parts - initialization, conditional, and iteration plus a body that is repeatedly executed as
/// long as the conditional expression evaluates to true. If the conditional is false at the start, the
/// body is never executed. Each of the 3 parts can be left blank, the first and last parts are actually
/// (comma-separated) lists of 0-N expressions, and the first part can also be a single <see cref="LocalDecl"/>.
/// </remarks>
public class For : BlockStatement
{
#region /* FIELDS */
/// <summary>
/// Can be either a LocalDecl or a ChildList of Expressions.
/// </summary>
protected object _initializations;
protected Expression _conditional;
protected ChildList<Expression> _iterations;
#endregion
#region /* CONSTRUCTORS */
/// <summary>
/// Create a <see cref="For"/> with the specified <see cref="CodeObject"/> in the body.
/// </summary>
public For(Expression initialization, Expression conditional, Expression iteration)
{
if (initialization != null)
Initializations.Add(initialization);
Conditional = conditional;
if (iteration != null)
Iterations.Add(iteration);
}
/// <summary>
/// Create a <see cref="For"/> with the specified <see cref="CodeObject"/> in the body.
/// </summary>
public For(LocalDecl initialization, Expression conditional, Expression iteration)
{
Initialization = initialization;
Conditional = conditional;
if (iteration != null)
Iterations.Add(iteration);
}
/// <summary>
/// Create a <see cref="For"/> with the specified <see cref="CodeObject"/> in the body.
/// </summary>
public For(Expression initialization, Expression conditional, Expression iteration, CodeObject body)
: base(body, true)
{
if (initialization != null)
Initializations.Add(initialization);
Conditional = conditional;
if (iteration != null)
Iterations.Add(iteration);
}
/// <summary>
/// Create a <see cref="For"/> with the specified <see cref="CodeObject"/> in the body.
/// </summary>
public For(LocalDecl initialization, Expression conditional, Expression iteration, CodeObject body)
: base(body, true)
{
Initialization = initialization;
Conditional = conditional;
if (iteration != null)
Iterations.Add(iteration);
}
#endregion
#region /* PROPERTIES */
/// <summary>
/// A <see cref="LocalDecl"/> used as the loop initializer.
/// </summary>
public LocalDecl Initialization
{
get { return (_initializations is LocalDecl ? (LocalDecl)_initializations : null); }
set
{
if (value != null && value.Parent != null)
throw new Exception("The LocalDecl used for the initialization variable of a For must be new, not one already owned by another Parent object.");
SetField(ref _initializations, value, true);
}
}
/// <summary>
/// A list of <see cref="Expression"/>s used as the loop initializer.
/// </summary>
public ChildList<Expression> Initializations
{
get
{
if (_initializations is LocalDecl)
return null;
if (_initializations == null)
_initializations = new ChildList<Expression>(this);
return (ChildList<Expression>)_initializations;
}
}
/// <summary>
/// The conditional <see cref="Expression"/>.
/// </summary>
public Expression Conditional
{
get { return _conditional; }
set { SetField(ref _conditional, value, true); }
}
/// <summary>
/// The list of <see cref="Expression"/>s used for the loop iterations section.
/// </summary>
public ChildList<Expression> Iterations
{
get
{
if (_iterations == null)
_iterations = new ChildList<Expression>(this);
return _iterations;
}
}
/// <summary>
/// The keyword associated with the <see cref="Statement"/>.
/// </summary>
public override string Keyword
{
get { return ParseToken; }
}
#endregion
#region /* METHODS */
/// <summary>
/// Deep-clone the code object.
/// </summary>
public override CodeObject Clone()
{
For clone = (For)base.Clone();
if (_initializations is ChildList<Expression>)
clone._initializations = ChildListHelpers.Clone((ChildList<Expression>)_initializations, clone);
else
clone.CloneField(ref clone._initializations, _initializations);
clone.CloneField(ref clone._conditional, _conditional);
clone._iterations = ChildListHelpers.Clone(_iterations, clone);
return clone;
}
#endregion
#region /* PARSING */
/// <summary>
/// The token used to parse the code object.
/// </summary>
public const string ParseToken = "for";
internal static void AddParsePoints()
{
Parser.AddParsePoint(ParseToken, Parse, typeof(IBlock));
}
/// <summary>
/// Parse a <see cref="For"/>.
/// </summary>
public static BlockStatement Parse(Parser parser, CodeObject parent, ParseFlags flags)
{
For @for = new For(parser, parent);
if (AutomaticCodeCleanup && !parser.IsGenerated)
{
// Normalize 'for (;;)' to 'while (true)' (with a null conditional)
if (@for._initializations == null && @for._conditional == null && @for._iterations == null && !@for.HasInfixComments)
{
While @while = new While(@for);
@while.SetLineCol(@for);
return @while;
}
}
return @for;
}
protected For(Parser parser, CodeObject parent)
: base(parser, parent)
{
parser.NextToken(); // Move past 'for'
ParseExpectedToken(parser, Expression.ParseTokenStartGroup);
// Parse either LocalDecl or Expression List
if (LocalDecl.PeekLocalDecl(parser))
_initializations = LocalDecl.Parse(parser, this, false, true);
else
_initializations = Expression.ParseList(parser, this, Expression.ParseTokenEndGroup);
if (_initializations == null)
MoveAllComments(parser.LastToken, false, false, AnnotationFlags.IsInfix1);
ParseExpectedToken(parser, Terminator); // Move past ';'
SetField(ref _conditional, Expression.Parse(parser, this, true, Terminator + Expression.ParseTokenEndGroup), false);
if (_conditional == null)
MoveAllComments(parser.LastToken, false, false, AnnotationFlags.IsInfix2);
ParseExpectedToken(parser, Terminator); // Move past ';'
_iterations = Expression.ParseList(parser, this, Expression.ParseTokenEndGroup);
if (_iterations == null)
MoveAllComments(parser.LastToken, false, false, AnnotationFlags.IsInfix3);
ParseExpectedToken(parser, Expression.ParseTokenEndGroup);
if (parser.TokenText == Terminator && !parser.Token.IsFirstOnLine)
ParseTerminator(parser); // Handle same-line ';' (null body)
else
new Block(out _body, parser, this, false); // 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 (_initializations is ChildList<Expression>)
ChildListHelpers.Resolve((ChildList<Expression>)_initializations, ResolveCategory.Expression, flags);
else if (_initializations is CodeObject)
_initializations = ((CodeObject)_initializations).Resolve(ResolveCategory.CodeObject, flags);
if (_conditional != null)
_conditional = (Expression)_conditional.Resolve(ResolveCategory.Expression, flags);
ChildListHelpers.Resolve(_iterations, ResolveCategory.Expression, 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)
{
if (_body != null)
{
_body.ResolveRef(name, resolver);
if (resolver.HasCompleteMatch) return; // Abort if we found a match
}
if (_initializations is LocalDecl)
{
((LocalDecl)_initializations).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 (_initializations is ChildList<Expression> && ChildListHelpers.HasUnresolvedRef((ChildList<Expression>)_initializations))
return true;
if (_initializations is CodeObject && ((CodeObject)_initializations).HasUnresolvedRef())
return true;
if (_conditional != null && _conditional.HasUnresolvedRef())
return true;
if (ChildListHelpers.HasUnresolvedRef(_iterations))
return true;
return base.HasUnresolvedRef();
}
#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="BlockStatement"/> always requires braces.
/// </summary>
public override bool HasBracesAlways
{
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
&& ((_initializations is ChildList<Expression> && (((ChildList<Expression>)_initializations).Count == 0
|| (!((ChildList<Expression>)_initializations)[0].IsFirstOnLine && ((ChildList<Expression>)_initializations).IsSingleLine)))
|| (_initializations is CodeObject && !((CodeObject)_initializations).IsFirstOnLine && ((CodeObject)_initializations).IsSingleLine)
|| _initializations == null)
&& (_conditional == null || (!_conditional.IsFirstOnLine && _conditional.IsSingleLine))
&& (_iterations == null || _iterations.Count == 0 || (!_iterations[0].IsFirstOnLine && _iterations.IsSingleLine)));
}
set
{
base.IsSingleLine = value;
if (value)
{
if (_initializations is ChildList<Expression>)
{
((ChildList<Expression>)_initializations)[0].IsFirstOnLine = false;
((ChildList<Expression>)_initializations).IsSingleLine = true;
}
else if (_initializations is CodeObject)
{
((CodeObject)_initializations).IsFirstOnLine = false;
((CodeObject)_initializations).IsSingleLine = true;
}
if (_conditional != null)
{
_conditional.IsFirstOnLine = false;
_conditional.IsSingleLine = true;
}
if (_iterations != null && _iterations.Count > 0)
{
_iterations[0].IsFirstOnLine = false;
_iterations.IsSingleLine = true;
}
}
}
}
#endregion
#region /* RENDERING */
protected override void AsTextArgument(CodeWriter writer, RenderFlags flags)
{
AsTextInfixComments(writer, AnnotationFlags.IsInfix1, flags);
if (Initialization != null)
Initialization.AsText(writer, flags);
else if (Initializations != null && Initializations.Count > 0)
writer.WriteList(Initializations, flags, this);
else
writer.Write(" ");
writer.Write(ParseTokenTerminator + " ");
AsTextInfixComments(writer, AnnotationFlags.IsInfix2, flags);
if (_conditional != null)
_conditional.AsText(writer, flags);
writer.Write(ParseTokenTerminator + " ");
AsTextInfixComments(writer, AnnotationFlags.IsInfix3, flags);
writer.WriteList(_iterations, flags, this);
}
#endregion
}
}