// 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.Reflection;
using Nova.Parsing;
using Nova.Rendering;
namespace Nova.CodeDOM
{
/// <summary>
/// The common base class of all expressions.
/// </summary>
/// <remarks>
/// Expressions often occur in trees, and can include operators, indexers, method (and property) calls, which operate
/// on variables, constants, fields, etc., resulting in a specific type and value.
/// Expressions which are valid as statements are: Assignment (and all compound assignments), Call,
/// Increment/Decrement, PostIncrement/Decrement, Dot, and NewObject.
/// </remarks>
public abstract class Expression : CodeObject
{
#region /* CONSTRUCTORS */
protected Expression()
{
SetFormatFlag(FormatFlags.Grouping, HasParensDefault);
}
#endregion
#region /* PROPERTIES */
/// <summary>
/// True if the expression is const.
/// </summary>
public virtual bool IsConst
{
get { return false; }
}
/// <summary>
/// True if the expression evaluates to a delegate type.
/// </summary>
public virtual bool IsDelegateType
{
get
{
TypeRefBase typeRefBase = SkipPrefixes() as TypeRefBase;
return (typeRefBase != null && typeRefBase.IsDelegateType);
}
}
/// <summary>
/// True if the expression evaluates to a delegate, or unresolved type or a <see cref="TypeParameterRef"/>.
/// </summary>
public virtual bool IsPossibleDelegateType
{
get { return IsDelegateType; }
}
#endregion
#region /* STATIC METHODS */
/// <summary>
/// Implicit conversion of a <see cref="Namespace"/> to an <see cref="Expression"/> (actually, a <see cref="NamespaceRef"/>).
/// </summary>
/// <remarks>This allows <see cref="Namespace"/> to be passed directly to any method expecting an <see cref="Expression"/> type
/// without having to create a reference first.</remarks>
/// <param name="namespace">The <see cref="Namespace"/> to be converted.</param>
/// <returns>A generated <see cref="NamespaceRef"/> to the specified <see cref="Namespace"/>.</returns>
public static implicit operator Expression(Namespace @namespace)
{
return @namespace.CreateRef();
}
/// <summary>
/// Implicit conversion of a <see cref="Type"/> to an <see cref="Expression"/> (actually, a <see cref="TypeRef"/>).
/// </summary>
/// <remarks>This allows <see cref="Type"/>s such as <c>typeof(int)</c> to be passed directly to any method
/// expecting an <see cref="Expression"/> type without having to create a reference first.</remarks>
/// <param name="type">The <see cref="Type"/> to be converted.</param>
/// <returns>A generated <see cref="TypeRef"/> to the specified <see cref="Type"/>.</returns>
public static implicit operator Expression(Type type)
{
if (type.IsGenericTypeDefinition)
{
// If the type is a generic type definition, such as 'typeof(Dictonary<,>)', then default the
// type arguments to 'null'. If the user needs the declared type arguments instead, they will
// have to call TypeRef.Create() directly.
return TypeRef.Create(type, ChildList<Expression>.CreateListOfNulls(type.GetGenericArguments().Length));
}
return TypeRef.Create(type);
}
/// <summary>
/// Implicit conversion of a <see cref="MethodBase"/> to an <see cref="Expression"/> (actually, a <see cref="MethodRef"/> or <see cref="ConstructorRef"/>).
/// </summary>
/// <remarks>This allows <see cref="MethodBase"/>s (<see cref="MethodInfo"/>s or <see cref="ConstructorInfo"/>s) to be passed directly
/// to any method expecting an <see cref="Expression"/> type without having to create a reference first.</remarks>
/// <param name="methodBase">The <see cref="MethodBase"/> to be converted.</param>
/// <returns>A generated <see cref="MethodRef"/> to the specified <see cref="MethodBase"/>.</returns>
public static implicit operator Expression(MethodBase methodBase)
{
return MethodRef.Create(methodBase);
}
/// <summary>
/// Implicit conversion of a <see cref="PropertyInfo"/> to an <see cref="Expression"/> (actually, a <see cref="PropertyRef"/>).
/// </summary>
/// <remarks>This allows <see cref="PropertyInfo"/>s to be passed directly to any method expecting an <see cref="Expression"/> type without
/// having to create a reference first.</remarks>
/// <param name="propertyInfo">The <see cref="PropertyInfo"/> to be converted.</param>
/// <returns>A generated <see cref="PropertyRef"/> to the specified <see cref="PropertyInfo"/>.</returns>
public static implicit operator Expression(PropertyInfo propertyInfo)
{
return new PropertyRef(propertyInfo);
}
/// <summary>
/// Implicit conversion of a <see cref="EventInfo"/> to an <see cref="Expression"/> (actually, a <see cref="EventRef"/>).
/// </summary>
/// <remarks>This allows <see cref="EventInfo"/>s to be passed directly to any method expecting an <see cref="Expression"/> type without
/// having to create a reference first.</remarks>
/// <param name="eventInfo">The <see cref="EventInfo"/> to be converted.</param>
/// <returns>A generated <see cref="EventRef"/> to the specified <see cref="EventInfo"/>.</returns>
public static implicit operator Expression(EventInfo eventInfo)
{
return new EventRef(eventInfo);
}
/// <summary>
/// Implicit conversion of a <see cref="FieldInfo"/> to an <see cref="Expression"/> (actually, a <see cref="FieldRef"/>).
/// </summary>
/// <remarks>This allows <see cref="FieldInfo"/>s to be passed directly to any method expecting an <see cref="Expression"/> type without
/// having to create a reference first.</remarks>
/// <param name="fieldInfo">The <see cref="FieldInfo"/> to be converted.</param>
/// <returns>A generated <see cref="FieldRef"/> to the specified <see cref="FieldInfo"/>.</returns>
public static implicit operator Expression(FieldInfo fieldInfo)
{
return new FieldRef(fieldInfo);
}
/// <summary>
/// Implicit conversion of a <see cref="TypeParameter"/> to an <see cref="Expression"/> (actually, a <see cref="TypeParameterRef"/>).
/// </summary>
/// <remarks>This allows <see cref="TypeParameter"/>s to be passed directly to any method expecting an <see cref="Expression"/> type
/// without having to create a reference first.</remarks>
/// <param name="typeParameter">The <see cref="TypeParameter"/> to be converted.</param>
/// <returns>A generated <see cref="TypeParameterRef"/> to the specified <see cref="TypeParameter"/>.</returns>
public static implicit operator Expression(TypeParameter typeParameter)
{
return typeParameter.CreateRef();
}
/// <summary>
/// Implicit conversion of a <see cref="Statement"/> to an <see cref="Expression"/>.
/// </summary>
/// <remarks>This allows declarations to be passed directly to any method expecting an <see cref="Expression"/>
/// type without having to create a reference first.</remarks>
/// <param name="statement">The <see cref="Statement"/> to be converted.</param>
/// <returns>A generated <see cref="SymbolicRef"/> to the specified <see cref="Statement"/>.</returns>
public static implicit operator Expression(Statement statement)
{
return statement.CreateRef();
}
/// <summary>
/// Implicit conversion of a <c>string</c> to an <see cref="Expression"/> (actually, a <see cref="Literal"/>).
/// </summary>
/// <remarks>This allows strings to be passed directly to any method expecting
/// an <see cref="Expression"/> type without having to do a <c>new Literal(value)</c>.</remarks>
/// <param name="value">The <c>string</c> to be converted.</param>
/// <returns>A generated <see cref="Literal"/> for the specified <c>string</c>.</returns>
public static implicit operator Expression(string value)
{
return new Literal(value);
}
/// <summary>
/// Implicit conversion of an <c>int</c> to an <see cref="Expression"/> (actually, a <see cref="Literal"/>).
/// </summary>
/// <remarks>This allows ints to be passed directly to any method expecting
/// an <see cref="Expression"/> type without having to do a <c>new Literal(value)</c>.</remarks>
/// <param name="value">The <c>int</c> to be converted.</param>
/// <returns>A generated <see cref="Literal"/> for the specified <c>int</c>.</returns>
public static implicit operator Expression(int value)
{
return new Literal(value);
}
/// <summary>
/// Implicit conversion of a <c>uint</c> to an <see cref="Expression"/> (actually, a <see cref="Literal"/>).
/// </summary>
/// <remarks>This allows uints to be passed directly to any method expecting
/// an <see cref="Expression"/> type without having to do a <c>new Literal(value)</c>.</remarks>
/// <param name="value">The <c>uint</c> to be converted.</param>
/// <returns>A generated <see cref="Literal"/> for the specified <c>uint</c>.</returns>
public static implicit operator Expression(uint value)
{
return new Literal(value);
}
/// <summary>
/// Implicit conversion of a <c>bool</c> to an <see cref="Expression"/> (actually, a <see cref="Literal"/>).
/// </summary>
/// <remarks>This allows bools to be passed directly to any method expecting
/// an <see cref="Expression"/> type without having to do a <c>new Literal(value)</c>.</remarks>
/// <param name="value">The <c>bool</c> to be converted.</param>
/// <returns>A generated <see cref="Literal"/> for the specified <c>bool</c>.</returns>
public static implicit operator Expression(bool value)
{
return new Literal(value);
}
/// <summary>
/// Implicit conversion of a <c>char</c> to an <see cref="Expression"/> (actually, a <see cref="Literal"/>).
/// </summary>
/// <remarks>This allows chars to be passed directly to any method expecting
/// an <see cref="Expression"/> type without having to do a <c>new Literal(value)</c>.</remarks>
/// <param name="value">The <c>char</c> to be converted.</param>
/// <returns>A generated <see cref="Literal"/> for the specified <c>char</c>.</returns>
public static implicit operator Expression(char value)
{
return new Literal(value);
}
/// <summary>
/// Implicit conversion of a <c>long</c> to an <see cref="Expression"/> (actually, a <see cref="Literal"/>).
/// </summary>
/// <remarks>This allows longs to be passed directly to any method expecting
/// an <see cref="Expression"/> type without having to do a <c>new Literal(value)</c>.</remarks>
/// <param name="value">The <c>long</c> to be converted.</param>
/// <returns>A generated <see cref="Literal"/> for the specified <c>long</c>.</returns>
public static implicit operator Expression(long value)
{
return new Literal(value);
}
/// <summary>
/// Implicit conversion of a <c>ulong</c> to an <see cref="Expression"/> (actually, a <see cref="Literal"/>).
/// </summary>
/// <remarks>This allows ulongs to be passed directly to any method expecting
/// an <see cref="Expression"/> type without having to do a <c>new Literal(value)</c>.</remarks>
/// <param name="value">The <c>ulong</c> to be converted.</param>
/// <returns>A generated <see cref="Literal"/> for the specified <c>ulong</c>.</returns>
public static implicit operator Expression(ulong value)
{
return new Literal(value);
}
/// <summary>
/// Implicit conversion of a <c>float</c> to an <see cref="Expression"/> (actually, a <see cref="Literal"/>).
/// </summary>
/// <remarks>This allows floats to be passed directly to any method expecting
/// an <see cref="Expression"/> type without having to do a <c>new Literal(value)</c>.</remarks>
/// <param name="value">The <c>float</c> to be converted.</param>
/// <returns>A generated <see cref="Literal"/> for the specified <c>float</c>.</returns>
public static implicit operator Expression(float value)
{
return new Literal(value);
}
/// <summary>
/// Implicit conversion of a <c>double</c> to an <see cref="Expression"/> (actually, a <see cref="Literal"/>).
/// </summary>
/// <remarks>This allows doubles to be passed directly to any method expecting
/// an <see cref="Expression"/> type without having to do a <c>new Literal(value)</c>.</remarks>
/// <param name="value">The <c>double</c> to be converted.</param>
/// <returns>A generated <see cref="Literal"/> for the specified <c>double</c>.</returns>
public static implicit operator Expression(double value)
{
return new Literal(value);
}
/// <summary>
/// Implicit conversion of a <c>decimal</c> to an <see cref="Expression"/> (actually, a <see cref="Literal"/>).
/// </summary>
/// <remarks>This allows decimals to be passed directly to any method expecting
/// an <see cref="Expression"/> type without having to do a <c>new Literal(value)</c>.</remarks>
/// <param name="value">The <c>decimal</c> to be converted.</param>
/// <returns>A generated <see cref="Literal"/> for the specified <c>decimal</c>.</returns>
public static implicit operator Expression(decimal value)
{
return new Literal(value);
}
#endregion
#region /* METHODS */
/// <summary>
/// Get the expression on the right of the right-most <see cref="Lookup"/> or <see cref="Dot"/> operator (bypass any '::' and '.' prefixes).
/// </summary>
public virtual Expression SkipPrefixes()
{
return this;
}
/// <summary>
/// Get the expression on the left of the left-most <see cref="Dot"/> operator.
/// </summary>
public virtual Expression FirstPrefix()
{
return this;
}
/// <summary>
/// Get the delegate parameters if the expression evaluates to a delegate type.
/// </summary>
public virtual ICollection GetDelegateParameters()
{
TypeRefBase typeRefBase = SkipPrefixes() as TypeRefBase;
return (typeRefBase != null ? typeRefBase.GetDelegateParameters() : null);
}
/// <summary>
/// Get the delegate return type if the expression evaluates to a delegate type.
/// </summary>
public virtual TypeRefBase GetDelegateReturnType()
{
TypeRefBase typeRefBase = SkipPrefixes() as TypeRefBase;
return (typeRefBase != null ? typeRefBase.GetDelegateReturnType() : null);
}
#endregion
#region /* PARSING */
/// <summary>
/// The token used to parse the start of a group.
/// </summary>
public const string ParseTokenStartGroup = "(";
/// <summary>
/// The token used to parse the end of a group.
/// </summary>
public const string ParseTokenEndGroup = ")";
/// <summary>
/// The token used to parse between expressions in a list.
/// </summary>
public const string ParseTokenSeparator = ",";
internal static void AddParsePoints()
{
// Use a parse-priority of 400 (ConstructorDecl uses 0, MethodDecl uses 50, LambdaExpression uses 100, Call uses 200, Cast uses 300)
Parser.AddParsePoint(ParseTokenStartGroup, 400, ParseParenthesizedExpression);
}
/// <summary>
/// Parse a parenthesized <see cref="Expression"/>.
/// </summary>
public static Expression ParseParenthesizedExpression(Parser parser, CodeObject parent, ParseFlags flags)
{
parser.SaveAndNextToken(); // Save '(' for now (remove when we find the matching close)
// Parse the expression with our parent set to block bubble-up normalization of EOL comments.
// This also handles proper parsing of nested Conditional expressions (resetting tracking if nested expressions have parens).
parser.PushNormalizationBlocker(parent);
Expression expression = Parse(parser, parent, false, ParseTokenEndGroup);
parser.PopNormalizationBlocker();
if (expression != null)
{
if (expression.ParseExpectedToken(parser, ParseTokenEndGroup)) // Move past ')'
{
Token open = parser.RemoveLastUnusedToken(); // Remove '('
expression.MoveFormatting(open);
expression.HasParens = true;
if (parser.LastToken.IsFirstOnLine)
expression.IsEndFirstOnLine = true;
// Move any comments after the '(' to the argument expression as regular (inline if necessary) comments
expression.MoveCommentsToLeftMost(open, true);
// Associate any trailing EOL or inline comment on the ')'.
// Skip this for directive expressions, because there is no terminating character for
// directive expressions to be associated with, and they'll end up here all the time.
if (!parser.InDirectiveExpression)
expression.MoveEOLComment(parser.LastToken);
}
}
return expression;
}
/// <summary>
/// Parse an expression, stopping when default terminators, or the specified terminators, or a higher-precedence
/// operator is encountered.
/// </summary>
/// <param name="parser">The parser object.</param>
/// <param name="parent">The parent object.</param>
/// <param name="isTopLevel">True if EOL comments can be associated with the expression during parsing - generally
/// true if the parent is a statement or expression list, but with some exceptions.</param>
/// <param name="terminator">Optional terminating characters (null if none).</param>
/// <param name="flags">Parsing flags.</param>
/// <returns>The parsed <see cref="Expression"/>.</returns>
public static Expression Parse(Parser parser, CodeObject parent, bool isTopLevel, string terminator, ParseFlags flags)
{
// Save the starting token of the expression for later
Token startingToken = parser.Token;
// Start a new Unused list in the parser
parser.PushUnusedList();
// Parse an expression, which can be in one of the following formats:
// - An identifier token, optionally followed by an operator (which is parsed only if precedence rules determine it should be)
// - An operator (which will itself look for previous and/or following expressions when parsed)
// - An open paren, expression, close paren sequence (handled by the installed parse-point above), optionally followed by an operator
// Any other sequence will cause parsing of the expression to cease.
// The expression will be terminated by any of ';,}]', or other specified terminator.
// Create a string of possible terminators (assuming 1 char terminators for now)
string terminators = Statement.ParseTokenTerminator + ParseTokenSeparator + Block.ParseTokenEnd + Index.ParseTokenEnd + terminator;
// Keep a reference to the last token so we can move any skipped non-EOL comments to the expression later
Token lastToken = parser.LastToken;
// Loop until EOF or we find a terminator, or for directive expressions stop if we find a comment or a token on a new line.
// NOTE: Keep this logic in sync with the 'if' statement further down in the loop that checks for termination.
while (parser.TokenText != null
&& (parser.TokenText.Length != 1 || terminators.IndexOf(parser.TokenText[0]) < 0)
&& (!parser.InDirectiveExpression || ((parser.LastToken.TrailingComments == null || parser.LastToken.TrailingComments.Count == 0) && !parser.Token.IsFirstOnLine)))
{
process_next:
bool skipTerminationCheck = false;
// Process the current token (will process operators)
CodeObject obj = parser.ProcessToken(parent, flags | ParseFlags.Expression);
if (obj != null)
{
// If we got something, save it for later.
// Don't move any EOL comments here - they should have already been processed.
if (obj is CompilerDirective)
{
// If we have a compiler directive, and there's a preceeding unused object, add it there
CodeObject lastUnusedCodeObject = parser.LastUnusedCodeObject;
if (lastUnusedCodeObject != null && !(lastUnusedCodeObject is CompilerDirective))
lastUnusedCodeObject.AttachAnnotation((CompilerDirective)obj, AnnotationFlags.IsPostfix);
else
{
parser.AddUnused(obj); // Add the object to the unused list
skipTerminationCheck = true;
}
}
else
{
obj.ParseUnusedAnnotations(parser, parent, true); // Parse any annotations from the Unused list
parser.AddUnused(obj); // Add the object to the unused list
}
}
// Stop if EOF or we find a terminator, or for directive expressions stop if we find a comment or a token on a new line.
// NOTE: Keep this logic in sync with that in the condition of the parent 'while' loop.
if (parser.TokenText == null
|| (parser.TokenText.Length == 1 && terminators.IndexOf(parser.TokenText[0]) >= 0)
|| (parser.InDirectiveExpression && ((parser.LastToken.TrailingComments != null && parser.LastToken.TrailingComments.Count != 0) || parser.Token.IsFirstOnLine)))
{
// Don't abort here on a '{' terminator if we're in a doc comment and we appear to have type arguments using
// braces (as opposed to an Initializer after a NewObject). Go process the next object immediately instead.
if (parser.InDocComment && parser.TokenText == TypeRefBase.ParseTokenAltArgumentStart && parser.HasUnusedIdentifier
&& TypeRefBase.PeekTypeArguments(parser, TypeRefBase.ParseTokenAltArgumentEnd, flags))
goto process_next;
break;
}
// If the current token is the start of a compiler directive, check for special situations in which we want to skip
// the termination check below. This allows the directive to be attached to preceeding code objects such as literals
// or operators, while not attaching to simple name or type expressions which might be part of a namespace or type header.
if (parser.TokenText == CompilerDirective.ParseToken)
{
CodeObject lastUnusedCodeObject = parser.LastUnusedCodeObject;
if (lastUnusedCodeObject is Literal || lastUnusedCodeObject is Operator)
{
skipTerminationCheck = true;
// Also, capture any pending trailing comments
if (obj != null)
obj.MoveCommentsAsPost(parser.LastToken);
}
}
// If we don't have a specific terminating character, then we're parsing a sub-expression and we should stop when we
// get to an invalid operator, or an operator of greater precedence. Skip this check if we just parsed a compiler
// directive and didn't have a preceeding code object to attach it to, or if we're about to parse a compiler directive
// and we have an unused code object that we'd like to attach it to.
if (terminator == null && !skipTerminationCheck)
{
// Check for '{' when used inside a doc comment in a generic type constructor or generic method instance
if (parser.InDocComment && parser.TokenText == TypeRefBase.ParseTokenAltArgumentStart
&& TypeRefBase.PeekTypeArguments(parser, TypeRefBase.ParseTokenAltArgumentEnd, flags))
continue;
// Check if the current token represents a valid operator
Parser.OperatorInfo operatorInfo = parser.GetOperatorInfoForToken();
// If the current token doesn't look like a valid operator, we're done with the expression
if (operatorInfo == null)
break;
// We have an operator - check if our parent is also an operator
if (parent is Operator)
{
// Special cases for Types: Some operator symbols are overloaded and can also be part
// of a type name. We must detect these here, and continue processing in these cases,
// skipping the operator precedence checks below that terminate the current expression.
// Check for '[' when used in an array type name
if (parser.TokenText == TypeRefBase.ParseTokenArrayStart && TypeRefBase.PeekArrayRanks(parser))
continue;
// Check for '<' when used in a generic type constructor or generic method instance
if (parser.TokenText == TypeRefBase.ParseTokenArgumentStart && TypeRefBase.PeekTypeArguments(parser, TypeRefBase.ParseTokenArgumentEnd, flags))
continue;
// Do NOT check for '?' used for nullable types, because it applies to the entire
// expression on the left, so we DO want to terminate processing.
// Determine the precedence of the parent operator
// NOTE: See the bottom of Operator.cs for a quick-reference of operator precedence.
int parentPrecedence = ((Operator)parent).GetPrecedence();
// Stop parsing if the parent operator has higher precedence
if (parentPrecedence < operatorInfo.Precedence)
break;
// If the parent has the same precedence, stop parsing if the operator is left-associative
if (parentPrecedence == operatorInfo.Precedence && operatorInfo.LeftAssociative)
break;
}
}
}
// Get the expression
Expression expression = parser.RemoveLastUnusedExpression();
if (expression != null)
{
// Attach any skipped non-EOL comments from the front of the expression, but only if we're a top-level expression
// (otherwise, comments that preceed a sub-expression will get attached to an outer expression instead). This
// prevents lost comments in places such as between a 'return' and the expression that follows.
if (isTopLevel)
expression.MoveAllComments(lastToken);
// If this is a top-level expression or if the next token is a close paren, move any trailing comments on the last
// token of the expression as post comments. This prevents lost comments in places such as when some trailing parts of
// an 'if' conditional expression are commented-out, or the trailing parts of any sub-expression before a close paren.
if ((isTopLevel || parser.TokenText == ParseTokenEndGroup) && parser.LastToken.HasTrailingComments && !parser.InDirectiveExpression)
expression.MoveCommentsAsPost(parser.LastToken);
}
// Flush remaining unused objects as Unrecognized objects
while (parser.HasUnused)
{
Expression preceedingUnused = parser.RemoveLastUnusedExpression(true);
if (preceedingUnused != null)
{
if (expression == null)
expression = new Unrecognized(false, parser.InDocComment, preceedingUnused);
else if (expression is Unrecognized && !expression.HasParens)
((Unrecognized)expression).AddLeft(preceedingUnused);
else
expression = new Unrecognized(false, parser.InDocComment, preceedingUnused, expression);
}
else
{
// If we have no expression to put them on, then parse any preceeding compiler directives into a temp object for later retrieval
if (expression == null)
expression = new TempExpr();
expression.ParseUnusedAnnotations(parser, parent, true);
break;
}
}
if (expression is Unrecognized)
((Unrecognized)expression).UpdateMessage();
parser.Unused.Clear();
// Restore the previous Unused list in the parser
parser.PopUnusedList();
if (expression != null)
{
// Get any EOL comments
if (parser.LastToken.HasTrailingComments)
expression.MoveEOLComment(parser.LastToken);
// Set the parent starting token to the beginning of the expression
parser.ParentStartingToken = startingToken;
}
return expression;
}
/// <summary>
/// Parse an expression, stopping when default terminators, or the specified terminators, or a higher-precedence
/// operator is encountered.
/// </summary>
/// <param name="parser">The parser object.</param>
/// <param name="parent">The parent object.</param>
/// <param name="isTopLevel">True if EOL comments can be associated with the expression during parsing - generally
/// true if the parent is a statement or expression list, but with some exceptions.</param>
/// <param name="terminator">Optional terminating characters (null if none).</param>
/// <returns>The parsed <see cref="Expression"/>.</returns>
public static Expression Parse(Parser parser, CodeObject parent, bool isTopLevel, string terminator)
{
return Parse(parser, parent, isTopLevel, terminator, ParseFlags.None);
}
/// <summary>
/// Parse an expression, stopping when default terminators, or the specified terminators, or a higher-precedence
/// operator is encountered.
/// </summary>
/// <param name="parser">The parser object.</param>
/// <param name="parent">The parent object.</param>
/// <param name="isTopLevel">True if EOL comments can be associated with the expression during parsing - generally
/// true if the parent is a statement or expression list, but with some exceptions.</param>
/// <returns>The parsed <see cref="Expression"/>.</returns>
public static Expression Parse(Parser parser, CodeObject parent, bool isTopLevel)
{
return Parse(parser, parent, isTopLevel, null, ParseFlags.None);
}
/// <summary>
/// Parse an expression, stopping when default terminators, or the specified terminators, or a higher-precedence
/// operator is encountered.
/// </summary>
/// <param name="parser">The parser object.</param>
/// <param name="parent">The parent object.</param>
/// <returns>The parsed <see cref="Expression"/>.</returns>
public static Expression Parse(Parser parser, CodeObject parent)
{
return Parse(parser, parent, false, null, ParseFlags.None);
}
/// <summary>
/// Parse an <see cref="Expression"/>.
/// </summary>
public static Expression Parse(Parser parser, CodeObject parent, bool isTopLevel, ParseFlags flags)
{
return Parse(parser, parent, isTopLevel, null, flags);
}
/// <summary>
/// Parse a directive <see cref="Expression"/>.
/// </summary>
public static Expression ParseDirectiveExpression(Parser parser, CodeObject parent)
{
parser.InDirectiveExpression = true;
Expression expression = Parse(parser, parent, true);
parser.InDirectiveExpression = false;
return expression;
}
/// <summary>
/// Parse a list of <see cref="Expression"/>s.
/// </summary>
public static ChildList<Expression> ParseList(Parser parser, CodeObject parent, string terminator, ParseFlags flags, bool allowSingleNullList)
{
ChildList<Expression> list = null;
bool skipStatementTerminators = (terminator == Initializer.ParseTokenEnd);
bool lastCommaFirstOnLine = false;
while (true)
{
Expression expression = Parse(parser, parent, true, terminator, flags);
bool hasComma = (parser.TokenText == ParseTokenSeparator);
if (expression != null)
{
// Force the expression to first-on-line if the last comma was (handles special-case
// formatting where the commas preceed the list items instead of following them).
if (lastCommaFirstOnLine)
expression.IsFirstOnLine = true;
// Get rid of any parens around the expression if they're not used on the code object by default
if (AutomaticFormattingCleanup && !parser.IsGenerated && expression.HasParens && !expression.HasParensDefault)
expression.HasParens = false;
}
if (expression is TempExpr)
{
// If we got a TempExpr, move any directives as postfix on the previous expression
if (list != null && list.Count > 0)
{
Expression previous = list.Last;
foreach (Annotation annotation in expression.Annotations)
{
if (annotation is CompilerDirective)
previous.AttachAnnotation(annotation, AnnotationFlags.IsPostfix);
}
}
}
else if (expression != null || allowSingleNullList || hasComma)
{
if (list == null)
list = new ChildList<Expression>(parent);
list.Add(expression);
}
// Continue processing if we have a ','. Also treat ';' like a comma if we're parsing an Initializer
// for better parsing of bad code (such as statements where an expression is expected).
if (hasComma || (skipStatementTerminators && parser.TokenText == Statement.ParseTokenTerminator))
{
lastCommaFirstOnLine = parser.Token.IsFirstOnLine;
parser.NextToken(); // Move past ',' (or ';')
if (expression != null)
{
// Move any EOL comment, and any regular comments as Post comments
expression.MoveEOLComment(parser.LastToken, false, false);
// Move any following regular comments as Post comments if on a line by themselves
if (parser.Token.IsFirstOnLine)
expression.MoveCommentsAsPost(parser.LastToken);
}
}
else
break;
}
return list;
}
/// <summary>
/// Parse a list of <see cref="Expression"/>s.
/// </summary>
public static ChildList<Expression> ParseList(Parser parser, CodeObject parent, string terminator)
{
return ParseList(parser, parent, terminator, ParseFlags.None, false);
}
/// <summary>
/// Move any comments from the specified <see cref="Token"/> to the left-most sub-expression.
/// </summary>
public virtual void MoveCommentsToLeftMost(Token token, bool skipParens)
{
MoveAllComments(token);
}
/// <summary>
/// Parse an <see cref="Expression"/>.
/// </summary>
protected Expression(Parser parser, CodeObject parent)
: base(parser, parent)
{ }
/// <summary>
/// Determine if the specified comment should be associated with the current code object during parsing.
/// </summary>
public override bool AssociateCommentWhenParsing(CommentBase comment)
{
// Only associate regular comments with expressions, not doc comments
return (comment is Comment);
}
#endregion
#region /* FORMATTING */
/// <summary>
/// Determines if the code object only requires a single line for display.
/// </summary>
public override bool IsSingleLine
{
get { return (base.IsSingleLine && !IsEndFirstOnLine); }
set
{
base.IsSingleLine = value;
if (value)
IsEndFirstOnLine = false;
}
}
/// <summary>
/// True if the code object defaults to starting on a new line.
/// </summary>
public override bool IsFirstOnLineDefault
{
get { return HasFirstOnLineAnnotations; }
}
/// <summary>
/// True if the expression should have parens by default.
/// </summary>
public virtual bool HasParensDefault
{
get { return false; } // Default is no parens
}
/// <summary>
/// True if the expression is surrounded by parens.
/// </summary>
public bool HasParens
{
get { return _formatFlags.HasFlag(FormatFlags.Grouping); }
set
{
SetFormatFlag(FormatFlags.Grouping, value);
_formatFlags |= FormatFlags.GroupingSet;
}
}
/// <summary>
/// True if the closing paren or bracket is on a new line.
/// </summary>
public virtual bool IsEndFirstOnLine
{
get { return _formatFlags.HasFlag(FormatFlags.InfixNewLine); }
set
{
SetFormatFlag(FormatFlags.InfixNewLine, value);
if (value)
_formatFlags |= FormatFlags.NewLinesSet;
}
}
/// <summary>
/// Default format the code object.
/// </summary>
protected internal override void DefaultFormat()
{
base.DefaultFormat();
// Default the parens if they haven't been explicitly set
if (!IsGroupingSet)
_formatFlags = ((_formatFlags & ~FormatFlags.Grouping) | (HasParensDefault ? FormatFlags.Grouping : 0));
}
/// <summary>
/// Format an expression assigned as an argument to another code object (turns off any parentheses).
/// </summary>
public void FormatAsArgument()
{
// Clear the grouping (parentheses) flag regardless of if it was manually set
_formatFlags &= ~(FormatFlags.Grouping | FormatFlags.GroupingSet);
}
#endregion
#region /* RENDERING */
public override void AsText(CodeWriter writer, RenderFlags flags)
{
int newLines = NewLines;
bool isPrefix = flags.HasFlag(RenderFlags.IsPrefix);
if (!isPrefix && newLines > 0)
{
if (!flags.HasFlag(RenderFlags.SuppressNewLine))
writer.WriteLines(newLines);
}
else if (flags.HasFlag(RenderFlags.PrefixSpace))
writer.Write(" ");
RenderFlags passFlags = (flags & RenderFlags.PassMask);
AsTextBefore(writer, passFlags | RenderFlags.IsPrefix);
// Increase the indent level for any newlines that occur within the expression
bool increaseIndent = flags.HasFlag(RenderFlags.IncreaseIndent);
if (increaseIndent)
writer.BeginIndentOnNewLine(this);
bool hasParens = HasParens;
if (hasParens)
writer.Write(ParseTokenStartGroup);
AsTextExpression(writer, passFlags | (flags & (RenderFlags.Attribute | RenderFlags.HasDotPrefix | RenderFlags.Declaration)));
if (hasParens)
{
if (IsEndFirstOnLine)
writer.WriteLine();
writer.Write(ParseTokenEndGroup);
}
if (HasTerminator && !flags.HasFlag(RenderFlags.Description))
{
writer.Write(Statement.ParseTokenTerminator);
CheckForAlignment(writer); // Check for alignment of any EOL comments
}
if (!flags.HasFlag(RenderFlags.NoEOLComments))
AsTextEOLComments(writer, flags);
AsTextAfter(writer, passFlags | (flags & RenderFlags.NoPostAnnotations));
if (increaseIndent)
writer.EndIndentation(this);
if (isPrefix)
{
// If this object is rendered as a child prefix object of another, then any whitespace is
// rendered here *after* the object instead of before it.
if (newLines > 0)
writer.WriteLines(newLines);
else if (!flags.HasFlag(RenderFlags.NoSpaceSuffix))
writer.Write(" ");
}
}
public abstract void AsTextExpression(CodeWriter writer, RenderFlags flags);
#endregion
}
}