// 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>
/// Includes a conditional expression plus a body (a statement or block) that is repeatedly executed
/// as long as the expression evaluates to true. If <see cref="IsDoWhile"/> is true, the expression
/// is evaluated at the bottom of the block ("do-while").
/// </summary>
/// <remarks>
/// When <see cref="IsDoWhile"/> is true, an instance of <see cref="DoWhile"/> is used internally to
/// represent the 'while' at the bottom of the loop, allowing for separate EOL comments and formatting
/// from the 'do' part of the loop. This object can be accessed with the <see cref="DoWhile"/> property.
///
/// A "do { } while (true)" or "for (;;) { }" is equivalent to a "while (true) { }", and is converted to
/// such upon parsing. A "while (expr);" has a null body and HasTerminator is true.
/// A "while (true) { }" statement is displayed as "do { }" in the GUI (an enhancement over standard C#).
/// </remarks>
public class While : BlockStatement
{
#region /* FIELDS */
protected Expression _conditional;
// A separate object is used for do-while loops so that the 'while' clause can have
// separate EOL comments and formatting (IsFirstOnLine) from the 'do'.
protected DoWhile _doWhile;
#endregion
#region /* CONSTRUCTORS */
/// <summary>
/// Create a <see cref="While"/>.
/// </summary>
public While(Expression expression, bool isDoWhile, CodeObject body)
: base(body, !isDoWhile) // Don't allow null body for do-while
{
Conditional = expression;
IsDoWhile = isDoWhile;
}
/// <summary>
/// Create a <see cref="While"/>.
/// </summary>
public While(Expression expression, bool isDoWhile)
: this(expression, isDoWhile, new Block())
{ }
/// <summary>
/// Create a <see cref="While"/>.
/// </summary>
public While(Expression expression, CodeObject body)
: this(expression, false, body)
{ }
/// <summary>
/// Create a <see cref="While"/>.
/// </summary>
public While(Expression expression)
: this(expression, false, new Block())
{ }
/// <summary>
/// Convert a For into a While.
/// </summary>
public While(For @for)
: base(@for)
{
// In addition to the body, also move any conditional expression, and move any iteration expressions
// into the end of the body. Any Initializations are ignored - they should be manually moved to the
// parent of the While.
Conditional = @for.Conditional;
if (@for.Iterations != null && @for.Iterations.Count > 0)
{
foreach (Expression expression in @for.Iterations)
Add(expression);
}
}
#endregion
#region /* PROPERTIES */
/// <summary>
/// The conditional <see cref="Expression"/>.
/// </summary>
public Expression Conditional
{
get { return _conditional; }
set
{
SetField(ref _conditional, value, true);
if (value == null)
IsDoWhile = false;
}
}
/// <summary>
/// True if there is a conditional <see cref="Expression"/>.
/// </summary>
public bool HasCondition
{
get { return (_conditional != null); }
}
/// <summary>
/// The <see cref="DoWhile"/> part if this is a 'do/while' loop.
/// </summary>
public DoWhile DoWhile
{
get { return _doWhile; }
set { SetField(ref _doWhile, value, false); }
}
/// <summary>
/// True if this is a 'do/while' loop.
/// </summary>
public bool IsDoWhile
{
get { return (_doWhile != null); }
set
{
if (value && HasCondition)
{
// Force to 'do-while' loop
if (_doWhile == null)
DoWhile = new DoWhile(this);
}
else
{
// Force to 'while' loop
_doWhile = null;
}
}
}
/// <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()
{
While clone = (While)base.Clone();
clone.CloneField(ref clone._conditional, _conditional);
clone.CloneField(ref clone._doWhile, _doWhile);
return clone;
}
#endregion
#region /* PARSING */
/// <summary>
/// The token used to parse the code object.
/// </summary>
public const string ParseToken = "while";
/// <summary>
/// The token used to parse a 'do' loop.
/// </summary>
public const string ParseTokenDo = "do";
internal static void AddParsePoints()
{
Parser.AddParsePoint(ParseToken, Parse, typeof(IBlock));
Parser.AddParsePoint(ParseTokenDo, ParseDoWhile, typeof(IBlock));
}
/// <summary>
/// Parse a <see cref="While"/>.
/// </summary>
public static While Parse(Parser parser, CodeObject parent, ParseFlags flags)
{
return new While(parser, parent, false);
}
/// <summary>
/// Parse a 'do/while' loop.
/// </summary>
public static While ParseDoWhile(Parser parser, CodeObject parent, ParseFlags flags)
{
return new While(parser, parent, true);
}
protected While(Parser parser, CodeObject parent, bool isDoWhile)
: base(parser, parent)
{
if (isDoWhile)
{
parser.NextToken(); // Move past 'do'
new Block(out _body, parser, this, false); // Parse the body
// Parse optional 'while' child part
if (parser.TokenText == ParseToken)
DoWhile = new DoWhile(parser, this);
if (AutomaticCodeCleanup && !parser.IsGenerated)
{
// Normalize 'do { } while (true)' to 'while (true)', and optimize to have a null condition (see below)
if (_conditional is Literal && ((Literal)_conditional).Text == Literal.ParseTokenTrue)
{
IsDoWhile = false;
_conditional = null;
}
}
}
else
{
ParseKeywordArgumentBody(parser, ref _conditional, true, false);
if (AutomaticCodeCleanup && !parser.IsGenerated)
{
// Optimize "while (true)" to have a null conditional expression, which represents an infinite loop
// (and optionally allows it to display as a "do" without a "while" condition in the GUI).
if (_conditional is Literal && ((Literal)_conditional).Text == Literal.ParseTokenTrue)
_conditional = null;
}
}
}
#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 (_conditional != null)
_conditional = (Expression)_conditional.Resolve(ResolveCategory.Expression, flags);
base.Resolve(ResolveCategory.CodeObject, flags);
if (_doWhile != null)
_doWhile.Resolve(ResolveCategory.CodeObject, flags);
return this;
}
/// <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 (_conditional != null && _conditional.HasUnresolvedRef())
return true;
if (_doWhile != null && _doWhile.HasUnresolvedRef())
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 (!IsDoWhile && HasCondition); }
}
/// <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 && (_conditional == null || (!_conditional.IsFirstOnLine && _conditional.IsSingleLine))
&& (_doWhile == null || (!_doWhile.IsFirstOnLine && _doWhile.IsSingleLine)));
}
set
{
base.IsSingleLine = value;
if (value)
{
if (_conditional != null)
{
_conditional.IsFirstOnLine = false;
_conditional.IsSingleLine = true;
}
if (_doWhile != null)
{
_doWhile.IsFirstOnLine = false;
_doWhile.IsSingleLine = true;
}
}
}
}
#endregion
#region /* RENDERING */
protected override void AsTextStatement(CodeWriter writer, RenderFlags flags)
{
UpdateLineCol(writer, flags);
if (HasCondition)
writer.Write(IsDoWhile ? ParseTokenDo : ParseToken);
else
{
// Always render a null condition as "while (true)" in text
writer.Write(ParseToken + " (true)");
}
}
protected override void AsTextArgument(CodeWriter writer, RenderFlags flags)
{
_conditional.AsText(writer, flags);
}
protected override void AsTextAfter(CodeWriter writer, RenderFlags flags)
{
base.AsTextAfter(writer, flags);
if (IsDoWhile && !flags.HasFlag(RenderFlags.Description))
_doWhile.AsText(writer, flags | RenderFlags.PrefixSpace);
}
#endregion
}
}