/// <summary> /// Parse a <see cref="Cast"/> operator. /// </summary> public static Cast Parse(Parser parser, CodeObject parent, ParseFlags flags) { // Verify that we have a pattern of "(Type)", otherwise abort (so the expression parens logic can try parsing it). // Do NOT set the ParseFlags.Type flag here, because it might not be a type (it might be "(A * B)"). // Also, verify that we're not inside a directive expression - casts aren't legal there. if (TypeRefBase.PeekType(parser, parser.PeekNextToken(), false, flags) && !parser.InDirectiveExpression) { Token last = parser.LastPeekedToken; if (last != null && last.Text == ParseTokenEnd) { // Verify that the cast is either followed by a non-symbol, or various legal symbols (those that // can only be unary operators). Token next = parser.PeekNextToken(); if (next != null) { if (!next.IsSymbol || next.Text == ParseTokenStartGroup || next.Text == Complement.ParseToken || next.Text == Increment.ParseToken || next.Text == Decrement.ParseToken || next.Text == Not.ParseToken) { return(new Cast(parser, parent)); } // In the case of the Negative and Positive unary operators following the Cast, // it's impossible to be sure they aren't actually binary operators, such as "(v1)-v2" or // "(v1)-(v2)" (yes, programmers actually write code like that for some reason!). // For now, assume if the operator is followed by a space and/or a '(', it's a binary // operator (so we don't have a Cast). This will cover most cases, but not 100%. // Any parse issues could be easily worked around by adding parens around the entire right // expression being cast, such as "(v1)(-(v2))" to force a cast, or around either both sides // or neither side of a binary operator to avoid a cast. if (next.Text == Positive.ParseToken || next.Text == Negative.ParseToken) { next = parser.PeekNextToken(); if (next.Text != ParseTokenStartGroup && next.LeadingWhitespace.Length == 0) { return(new Cast(parser, parent)); } } } // Otherwise, fail and treat it as a grouped expression instead. } } return(null); }
/// <summary> /// Peek ahead at the input tokens to determine if they look like a valid LocalDecl. /// </summary> public static bool PeekLocalDecl(Parser parser) { bool valid = false; // Validate that we have what appears to be a valid Type followed by an identifier if (TypeRefBase.PeekType(parser, parser.Token, false, ParseFlags.Type)) { Token next = parser.LastPeekedToken; if (next != null && next.IsIdentifier) { // Also validate that it's followed by one of ";=)," if (";=),".Contains(parser.PeekNextTokenText())) { valid = true; } } } return(valid); }
/// <summary> /// Parse a <see cref="NewObject"/> or <see cref="NewArray"/> operator. /// </summary> public static NewOperator Parse(Parser parser, CodeObject parent, ParseFlags flags) { // Abort if our parent is a TypeDecl (the 'new' is probably part of a method declaration) if (parent is TypeDecl) { return(null); } NewOperator result = null; // Peek ahead to see if we have a valid non-array type TypeRefBase.PeekType(parser, parser.PeekNextToken(), true, flags | ParseFlags.Type); Token token = parser.LastPeekedToken; if (token != null) { // If we found a '[', assume NewArray if (token.Text == NewArray.ParseTokenStart) { result = new NewArray(parser, parent); } // If we found '(' or '{', assume NewObject else if (token.Text == ParameterDecl.ParseTokenStart || token.Text == Initializer.ParseTokenStart) { result = new NewObject(parser, parent); } } // Last chance - invalid code might still parse better as a NewObject, so assume that's // what it is if our parent is a VariableDecl. if (result == null && parent is VariableDecl) { result = new NewObject(parser, parent); } // If we didn't create an object, return null (the 'new' is probably part of a method declaration) return(result); }
protected static bool PeekConditional(Parser parser, CodeObject parent, int colonCount, ParseFlags flags) { // Unfortunately, determining if the '?' is definitely part of a '?:' pair as opposed to a nullable type declaration // isn't easy - in fact, it's the single hardest thing to parse in the entire language. Nicely formatted code always // has a space before it in the first case, and not in the second, but code is often poorly formatted. The only way // to be sure how to parse it is to peek ahead looking for the matching ':'. We can parse in a very simplified manner // for efficiency, just keeping track of '()', '[]', '{}' pairs, aborting if we hit a ';' anywhere, or a ',' that's // not in a nested scope, or finally if we find the matching ':' (not in a nested scope). If we're in the '?' clause // of a nested Conditional without parens, then we need to find an extra ':' for each nested level (colonCount). // One more thing - we have to handle '<>' with generic arguments in order to avoid aborting on a possible ',' // inside them, but we also have to avoid any confusion with LessThan/GreatherThan operators. bool firstToken = true; Stack <string> stack = new Stack <string>(8); while (true) { Token next = parser.PeekNextToken(); if (next == null) { break; } check: if (next.IsSymbol) { // Abort if any invalid symbols appear immediately after the '?' if (firstToken) { firstToken = false; if (">)]};,".Contains(next.Text)) { break; } } // If we have a '<', skip over any possible generic type parameters so that we don't abort on a ',' if (next.Text == TypeRefBase.ParseTokenArgumentStart) { TypeRefBase.PeekTypeArguments(parser, TypeRefBase.ParseTokenArgumentEnd, flags); next = parser.LastPeekedToken; } // Keep track of nested parens, brackets (new arrays), braces (initializers or generics in doc comments) string nextText = next.Text; if (nextText == ParseTokenStartGroup || nextText == NewArray.ParseTokenStart || nextText == Initializer.ParseTokenStart) { stack.Push(nextText); } else if (nextText == ParseTokenEndGroup) { // If we hit an unexpected ')', abort if (stack.Count == 0 || stack.Peek() != ParseTokenStartGroup) { break; } stack.Pop(); } else if (nextText == NewArray.ParseTokenEnd) { // If we hit an unexpected ']', abort if (stack.Count == 0 || stack.Peek() != NewArray.ParseTokenStart) { break; } stack.Pop(); } else if (nextText == Initializer.ParseTokenEnd) { // If we hit an unexpected '}', abort if (stack.Count == 0 || stack.Peek() != Initializer.ParseTokenStart) { break; } stack.Pop(); } else if (nextText == ParseToken1) { // If we found a '?', recursively call this routine to process it (in order to // differentiate between a nested nullable type or another Conditional). if (!PeekConditional(parser, parent, 1, flags)) { // If it wasn't a Conditional, get the last token and check it next = parser.LastPeekedToken; goto check; } } else if (stack.Count == 0) { // We're not inside any nested parens/brackets/braces/angle brackets. Check for certain symbols: // Terminate on a ',' or ';' (we ignore if nested because of anonymous method bodies) if (nextText == ParseTokenSeparator || nextText == Statement.ParseTokenTerminator) { break; } // Process a ':' if (nextText == ParseToken2) { // Assume we have a valid Conditional if the expected number of colons has been found if (--colonCount == 0) { return(true); } } } } else if (next.Text == NewOperator.ParseToken) { // If we found a 'new', treat the following as a type (in order to avoid any trailing '?' of // a nullable type from being treated as a nested conditional). TypeRefBase.PeekType(parser, parser.PeekNextToken(), true, flags | ParseFlags.Type); // Whether it worked or not, pick up with the last peeked token next = parser.LastPeekedToken; firstToken = false; goto check; } firstToken = false; } return(false); }