/* Function: TryToGetLineComment * If the iterator is on a line that starts with a line comment symbol, this function moves the iterator past the entire * comment and returns true. If the comment is a candidate for documentation it will also return it as a * <PossibleDocumentationComment>. If the line does not start with a line comment symbol it will return false and * leave the iterator where it is. */ protected bool TryToGetLineComment(ref LineIterator lineIterator, string commentSymbol, out PossibleDocumentationComment comment) { TokenIterator firstToken = lineIterator.FirstToken(LineBoundsMode.ExcludeWhitespace); if (firstToken.MatchesAcrossTokens(commentSymbol) == false) { comment = null; return(false); } comment = new PossibleDocumentationComment(); comment.Start = lineIterator; lineIterator.Next(); // Since we're definitely returning a comment we can mark the comment symbols as we go rather than waiting until // the end. firstToken.SetCommentParsingTypeByCharacters(CommentParsingType.CommentSymbol, commentSymbol.Length); while (lineIterator.IsInBounds) { firstToken = lineIterator.FirstToken(LineBoundsMode.ExcludeWhitespace); if (firstToken.MatchesAcrossTokens(commentSymbol) == false) { break; } firstToken.SetCommentParsingTypeByCharacters(CommentParsingType.CommentSymbol, commentSymbol.Length); lineIterator.Next(); } comment.End = lineIterator; return(true); }
/* Function: TryToSkipSubstitutionIdentifier * If the iterator is on a valid substitution identifier, advances it past it, returns it, and returns true. * Otherwise the iterator will be left alone and it will return false. */ protected bool TryToSkipSubstitutionIdentifier(ref TokenIterator iterator, out string identifier) { if (iterator.Character != '$' && iterator.Character != '@') { identifier = null; return(false); } TokenIterator lookahead = iterator; lookahead.Next(); // Locale substitutions if (lookahead.MatchesAcrossTokens("Locale{")) { identifier = null; return(false); } while (lookahead.IsInBounds && (lookahead.FundamentalType == FundamentalType.Text || lookahead.Character == '.' || lookahead.Character == '_')) { lookahead.Next(); } identifier = iterator.TextBetween(lookahead); iterator = lookahead; return(true); }
/* Function: IsOnOpeningBlockCommentSymbol * * Returns whether the iterator is on an opening block comment symbol. This handles --[[ comments as well as forms with * equals signs such as --[==[. */ protected bool IsOnOpeningBlockCommentSymbol(TokenIterator iterator) { if (iterator.MatchesAcrossTokens("--[") == false) { return(false); } TokenIterator lookahead = iterator; lookahead.Next(3); while (lookahead.Character == '=') { lookahead.Next(); } if (lookahead.Character == '[') { return(true); } else { return(false); } }
/* Function: TryToGetPDLineComment * * If the line iterator is on a line comment, return it and all connected line comments as a * <PossibleDocumentationComment> and mark the symbols as <CommentParsingType.CommentSymbol>. Returns * null otherwise. * * This function takes a separate comment symbol for the first line and all remaining lines, allowing you to detect * Javadoc line comments that start with ## and the remaining lines use #. Both symbols can be the same if this isn't * required. If openingMustBeAlone is set, no symbol can appear immediately after the first line symbol for this * function to succeed. This allows you to specifically detect something like ## without also matching #######. */ protected PossibleDocumentationComment TryToGetPDLineComment(LineIterator lineIterator, string firstSymbol, string remainderSymbol, bool openingMustBeAlone) { TokenIterator firstToken = lineIterator.FirstToken(LineBoundsMode.ExcludeWhitespace); if (firstToken.MatchesAcrossTokens(firstSymbol) == false) { return(null); } if (openingMustBeAlone) { TokenIterator nextToken = firstToken; nextToken.NextByCharacters(firstSymbol.Length); if (nextToken.FundamentalType == FundamentalType.Symbol) { return(null); } } PossibleDocumentationComment comment = new PossibleDocumentationComment(); comment.Start = lineIterator; lineIterator.Next(); // Since we're definitely returning a comment (barring the operation being cancelled) we can mark the comment // symbols as we go rather than waiting until the end. firstToken.SetCommentParsingTypeByCharacters(CommentParsingType.CommentSymbol, firstSymbol.Length); while (lineIterator.IsInBounds) { firstToken = lineIterator.FirstToken(LineBoundsMode.ExcludeWhitespace); if (firstToken.MatchesAcrossTokens(remainderSymbol) == false) { break; } firstToken.SetCommentParsingTypeByCharacters(CommentParsingType.CommentSymbol, remainderSymbol.Length); lineIterator.Next(); } comment.End = lineIterator; return(comment); }
/* Function: TryToSkipLineCommentSymbol * * If the iterator is on a line comment, moves it past it and returns true. Otherwise leaves the iterator alone and * returns false. This handles the fact that block comments also start with -- and will not move past them. */ protected bool TryToSkipLineCommentSymbol(ref TokenIterator iterator) { if (iterator.MatchesAcrossTokens("--") && !IsOnOpeningBlockCommentSymbol(iterator)) { iterator.Next(2); return(true); } else { return(false); } }
/* Function: TryToSkipBlockComment * If the iterator is on the opening symbol of a block comment, skips over it and returns true. Otherwise leaves the iterator * alone and returns false. */ protected bool TryToSkipBlockComment(ref TokenIterator iterator, string openingSymbol, string closingSymbol) { if (iterator.MatchesAcrossTokens(openingSymbol)) { iterator.NextByCharacters(openingSymbol.Length); while (iterator.IsInBounds && !iterator.MatchesAcrossTokens(closingSymbol)) { iterator.Next(); } if (iterator.IsInBounds) { iterator.NextByCharacters(closingSymbol.Length); } return(true); } else { return(false); } }
/* Function: TryToSkipOpeningBlockCommentSymbol * * If the iterator is on an opening block comment symbol, moves it past it and returns true and the closing symbol. Otherwise * leaves the iterator alone and returns false and null. This handles --[[ comments as well as forms with equals signs such as * --[==[. */ protected bool TryToSkipOpeningBlockCommentSymbol(ref TokenIterator iterator, out string closingSymbol) { if (iterator.MatchesAcrossTokens("--[") == false) { closingSymbol = null; return(false); } TokenIterator lookahead = iterator; lookahead.Next(3); int equals = 0; while (lookahead.Character == '=') { lookahead.Next(); equals++; } if (lookahead.Character != '[') { closingSymbol = null; return(false); } lookahead.Next(); iterator = lookahead; if (equals == 0) { closingSymbol = "]]"; } else { StringBuilder closingSymbolBuilder = new StringBuilder(2 + equals); closingSymbolBuilder.Append(']'); closingSymbolBuilder.Append('=', equals); closingSymbolBuilder.Append(']'); closingSymbol = closingSymbolBuilder.ToString(); } return(true); }
/* Function: TryToSkipLocaleSubstitutionIdentifier * If the iterator is on a valid locale substitution identifier, advances it past it, returns it, and returns true. * Otherwise the iterator will be left alone and it will return false. */ protected bool TryToSkipLocaleSubstitutionIdentifier(ref TokenIterator iterator, out string identifier, out string localeIdentifier) { if (iterator.Character != '$' && iterator.Character != '@') { identifier = null; localeIdentifier = null; return(false); } TokenIterator lookahead = iterator; lookahead.Next(); if (lookahead.MatchesAcrossTokens("Locale{") == false) { identifier = null; localeIdentifier = null; return(false); } lookahead.NextByCharacters(7); TokenIterator startOfLocaleIdentifier = lookahead; while (lookahead.IsInBounds && lookahead.Character != '}') { lookahead.Next(); } if (lookahead.Character != '}') { identifier = null; localeIdentifier = null; return(false); } localeIdentifier = startOfLocaleIdentifier.TextBetween(lookahead); lookahead.Next(); identifier = iterator.TextBetween(lookahead); iterator = lookahead; return(true); }
/* Function: TryToSkipLineComment * If the iterator is on the opening symbol of a line comment, skips over it and returns true. Otherwise leaves the iterator * alone and returns false. */ protected bool TryToSkipLineComment(ref TokenIterator iterator, string symbol) { if (iterator.MatchesAcrossTokens(symbol)) { iterator.NextByCharacters(symbol.Length); while (iterator.IsInBounds && iterator.FundamentalType != FundamentalType.LineBreak) { iterator.Next(); } if (iterator.FundamentalType == FundamentalType.LineBreak) { iterator.Next(); } return(true); } else { return(false); } }
// Function: GetPossibleDocumentationComments // // Goes through the file looking for comments that could possibly contain documentation and returns them as a list. These // comments are not guaranteed to have documentation in them, just to be acceptable candidates for them. If there are no // comments it will return an empty list. // // All the comments in the returned list will have their comment symbols marked as <CommentParsingType.CommentSymbol> // in the tokenizer. This allows further operations to be done on them in a language independent manner. If you want to also // filter out text boxes and lines, use <Comments.LineFinder>. // override public List <PossibleDocumentationComment> GetPossibleDocumentationComments(Tokenizer source) { List <PossibleDocumentationComment> comments = new List <PossibleDocumentationComment>(); LineIterator lineIterator = source.FirstLine; PODLineType podLineType; while (lineIterator.IsInBounds) { TokenIterator tokenIterator = lineIterator.FirstToken(LineBoundsMode.ExcludeWhitespace); // Hash comments if (tokenIterator.Character == '#') { PossibleDocumentationComment comment = new PossibleDocumentationComment(); comment.Start = lineIterator; // First line if (tokenIterator.MatchesAcrossTokens("###")) { comment.Javadoc = false; comment.XML = true; } else if (tokenIterator.MatchesAcrossTokens("##")) { comment.Javadoc = true; comment.XML = false; } else // just "#" { comment.Javadoc = false; comment.XML = false; } lineIterator.Next(); // Subsequent lines while (lineIterator.IsInBounds) { tokenIterator = lineIterator.FirstToken(LineBoundsMode.ExcludeWhitespace); if (tokenIterator.Character != '#') { break; } if (tokenIterator.MatchesAcrossTokens("###")) { comment.Javadoc = false; // XML is still possible } else if (tokenIterator.MatchesAcrossTokens("##")) { comment.Javadoc = false; comment.XML = false; } else // just "#" { // Javadoc is still possible comment.XML = false; } lineIterator.Next(); } comment.End = lineIterator; comments.Add(comment); // Go back and mark the tokens int firstLineCount, subsequentLineCount; if (comment.XML) { firstLineCount = 3; subsequentLineCount = 3; } else if (comment.Javadoc) { firstLineCount = 2; subsequentLineCount = 1; } else // plain { firstLineCount = 1; subsequentLineCount = 1; } LineIterator temp = comment.Start; tokenIterator = temp.FirstToken(LineBoundsMode.ExcludeWhitespace); tokenIterator.SetCommentParsingTypeByCharacters(CommentParsingType.CommentSymbol, firstLineCount); temp.Next(); while (temp < comment.End) { tokenIterator = temp.FirstToken(LineBoundsMode.ExcludeWhitespace); tokenIterator.SetCommentParsingTypeByCharacters(CommentParsingType.CommentSymbol, subsequentLineCount); temp.Next(); } // XML can actually appear in Javadoc comments if (comment.Javadoc) { comment.XML = true; } } // POD comments else if (TryToSkipPODLine(ref tokenIterator, out podLineType)) { TokenIterator podLineStart, podLineEnd; lineIterator.GetBounds(LineBoundsMode.CommentContent, out podLineStart, out podLineEnd); lineIterator.Tokenizer.SetCommentParsingTypeBetween(podLineStart, podLineEnd, CommentParsingType.CommentSymbol); if (podLineType == PODLineType.StartNaturalDocs || podLineType == PODLineType.StartJavadoc) { PossibleDocumentationComment comment = new PossibleDocumentationComment(); comment.Start = lineIterator; if (podLineType == PODLineType.StartJavadoc) { comment.Javadoc = true; } for (;;) { lineIterator.Next(); if (lineIterator.IsInBounds == false) { break; } tokenIterator = lineIterator.FirstToken(LineBoundsMode.CommentContent); if (TryToSkipPODLine(ref tokenIterator, out podLineType) == true) { if (podLineType == PODLineType.End) { lineIterator.GetBounds(LineBoundsMode.CommentContent, out podLineStart, out podLineEnd); lineIterator.Tokenizer.SetCommentParsingTypeBetween(podLineStart, podLineEnd, CommentParsingType.CommentSymbol); lineIterator.Next(); } break; } } comment.End = lineIterator; comments.Add(comment); } else { lineIterator.Next(); } } else { lineIterator.Next(); } } return(comments); }
/* Function: AppendCellContents */ protected void AppendCellContents(int parameterIndex, int cellIndex, StringBuilder output) { TokenIterator start = parameterTableSection.Start; start.Next(parameterTableTokenIndexes[parameterIndex, cellIndex] - start.TokenIndex); TokenIterator end = start; end.Next(parameterTableTokenIndexes[parameterIndex, cellIndex + 1] - end.TokenIndex); bool hadTrailingWhitespace = end.PreviousPastWhitespace(PreviousPastWhitespaceMode.EndingBounds, start); ColumnType type = ColumnOrder[cellIndex]; // Find the type of the next used cell ColumnType?nextType = null; for (int nextCellIndex = cellIndex + 1; nextCellIndex < NumberOfColumns; nextCellIndex++) { if (parameterTableColumnsUsed[nextCellIndex]) { nextType = ColumnOrder[nextCellIndex]; break; } } // Default value separators always get spaces before. // Property value separators get them unless they're ":", but watch out for ":=". // Type-name separators get them if they're text (SQL's "AS") instead of symbols (Pascal's ":"). if (type == ColumnType.DefaultValueSeparator || (type == ColumnType.PropertyValueSeparator && (start.Character != ':' || start.MatchesAcrossTokens(":="))) || (type == ColumnType.TypeNameSeparator && start.FundamentalType == FundamentalType.Text)) { output.Append(" "); } // We don't want to highlight keywords on the Name cell because identifiers can accidentally be marked as them with // basic language support, such as "event" in "wxPaintEvent &event". if (type == ColumnType.Name) { AppendSyntaxHighlightedText(start, end, output, excludeKeywords: true); } else if (addLinks) { AppendSyntaxHighlightedTextWithTypeLinks(start, end, output, links, linkTargets, extendTypeSearch: true); } else { AppendSyntaxHighlightedText(start, end, output); } // Default value separators, property value separators, and type/name separators always get spaces after. Make sure // the spaces aren't duplicated by the preceding cells. if (type == ColumnType.DefaultValueSeparator || type == ColumnType.PropertyValueSeparator || type == ColumnType.TypeNameSeparator || (hadTrailingWhitespace && type != ColumnType.DefaultValue && nextType != ColumnType.DefaultValueSeparator && nextType != ColumnType.PropertyValueSeparator && nextType != ColumnType.TypeNameSeparator)) { output.Append(" "); } }
/* Function: TryToGetBlockSymbol * If the iterator is on the symbol part of a block tag that has one, such as "@param symbol description", extracts the symbol, * moves the iterator past it, and returns true. */ protected bool TryToGetBlockSymbol(ref TokenIterator iterator, TokenIterator limit, out string entryText) { TokenIterator lookahead = iterator; // Javadoc recommends documenting template parameters as "@param <T> ...". if (lookahead.Character == '<') { lookahead.Next(); for (;;) { if (lookahead >= limit) { entryText = null; return(false); } else if (lookahead.Character == '>') { lookahead.Next(); break; } else { lookahead.Next(); } } } else // not '<' { for (;;) { if (lookahead >= limit) { entryText = null; return(false); } else if (lookahead.FundamentalType == FundamentalType.Text || lookahead.Character == '_' || lookahead.Character == '.') { lookahead.Next(); } else if (lookahead.MatchesAcrossTokens("::") || lookahead.MatchesAcrossTokens("->")) { lookahead.NextByCharacters(2); } else { break; } } } if (lookahead >= limit || lookahead.FundamentalType != FundamentalType.Whitespace) { entryText = null; return(false); } entryText = iterator.TextBetween(lookahead); iterator = lookahead; return(true); }
// Group: Support Functions // __________________________________________________________________________ /* Function: TryToGetPDBlockComment * * If the line iterator is on the starting symbol of a block comment, return it as a <PossibleDocumentationComment> * and mark the symbols as <CommentParsingType.CommentSymbol>. If the iterator is not on the opening comment * symbol or there is content after the closing comment symbol making it unsuitable as a documentation comment, * returns null. * * If openingMustBeAlone is set, that means no symbol can appear immediately after the opening symbol for this * function to succeed. This allows you to specifically detect something like /** without also matching /******. */ protected PossibleDocumentationComment TryToGetPDBlockComment(LineIterator lineIterator, string openingSymbol, string closingSymbol, bool openingMustBeAlone) { TokenIterator firstToken = lineIterator.FirstToken(LineBoundsMode.ExcludeWhitespace); if (firstToken.MatchesAcrossTokens(openingSymbol) == false) { return(null); } if (openingMustBeAlone) { TokenIterator nextToken = firstToken; nextToken.NextByCharacters(openingSymbol.Length); if (nextToken.FundamentalType == FundamentalType.Symbol) { return(null); } } PossibleDocumentationComment comment = new PossibleDocumentationComment(); comment.Start = lineIterator; for (;;) { if (!lineIterator.IsInBounds) { return(null); } TokenIterator closingSymbolIterator; if (lineIterator.FindAcrossTokens(closingSymbol, false, LineBoundsMode.Everything, out closingSymbolIterator) == true) { closingSymbolIterator.NextByCharacters(closingSymbol.Length); closingSymbolIterator.NextPastWhitespace(); if (closingSymbolIterator.FundamentalType != FundamentalType.LineBreak && closingSymbolIterator.FundamentalType != FundamentalType.Null) { return(null); } lineIterator.Next(); comment.End = lineIterator; break; } lineIterator.Next(); } // Success. Mark the symbols before returning. firstToken.SetCommentParsingTypeByCharacters(CommentParsingType.CommentSymbol, openingSymbol.Length); TokenIterator lastToken; lineIterator.Previous(); lineIterator.GetBounds(LineBoundsMode.ExcludeWhitespace, out firstToken, out lastToken); lastToken.PreviousByCharacters(closingSymbol.Length); lastToken.SetCommentParsingTypeByCharacters(CommentParsingType.CommentSymbol, closingSymbol.Length); return(comment); }
/* Function: AddToken * Adds a single token to the type builder. You must use <AddBlock()> for blocks. */ public void AddToken(TokenIterator iterator) { if (iterator.PrototypeParsingType == PrototypeParsingType.OpeningTypeModifier || iterator.PrototypeParsingType == PrototypeParsingType.OpeningParamModifier) { throw new InvalidOperationException(); } if (iterator.PrototypeParsingType == PrototypeParsingType.Type || iterator.PrototypeParsingType == PrototypeParsingType.TypeModifier || iterator.PrototypeParsingType == PrototypeParsingType.TypeQualifier || iterator.PrototypeParsingType == PrototypeParsingType.ParamModifier || iterator.PrototypeParsingType == PrototypeParsingType.StartOfTuple || iterator.PrototypeParsingType == PrototypeParsingType.EndOfTuple || iterator.PrototypeParsingType == PrototypeParsingType.TupleMemberSeparator || iterator.PrototypeParsingType == PrototypeParsingType.TupleMemberName) { FundamentalType thisTokenType = (iterator.Character == '_' ? FundamentalType.Text : iterator.FundamentalType); // ignore whitespace, we'll create it as necessary if (thisTokenType == FundamentalType.Text || thisTokenType == FundamentalType.Symbol) { // Do we need to add a space? if ((thisTokenType == FundamentalType.Text && lastTokenType == FundamentalType.Text && (iterator.Tokenizer != lastTokenIterator.Tokenizer || iterator.TokenIndex != lastTokenIterator.TokenIndex + 1)) || (thisTokenType == FundamentalType.Text && lastTokenType == FundamentalType.Symbol && !dontAddSpaceAfterSymbol && (pastFirstText || lastSymbolWasBlock)) || (!lastTokenIterator.IsNull && lastTokenIterator.PrototypeParsingType == PrototypeParsingType.TupleMemberSeparator)) { rawText.Append(' '); prototypeParsingTypes.Add(PrototypeParsingType.Null); syntaxHighlightingTypes.Add(SyntaxHighlightingType.Null); } // Special handling for package separators and a few other things if (iterator.FundamentalType == FundamentalType.Symbol) { if (iterator.Character == '.' || iterator.MatchesAcrossTokens("::") || (iterator.Character == ':' && dontAddSpaceAfterSymbol) || // second colon of :: iterator.Character == '%' || // used for MyVar%TYPE or MyTable%ROWTYPE in Oracle's PL/SQL iterator.Character == '"' || iterator.Character == '\'' || // strings in Java annotations like @copyright("me") iterator.Character == '@' || // tags in Java annotations like @copyright iterator.Character == '(') // parens around tuples { dontAddSpaceAfterSymbol = true; } else { dontAddSpaceAfterSymbol = false; } } iterator.AppendTokenTo(rawText); prototypeParsingTypes.Add(iterator.PrototypeParsingType); syntaxHighlightingTypes.Add(iterator.SyntaxHighlightingType); lastTokenIterator = iterator; lastTokenType = thisTokenType; lastSymbolWasBlock = false; if (thisTokenType == FundamentalType.Text) { pastFirstText = true; } } } }
/* Function: HasSimilarSpacing * Returns whether the spacing of the tokens between the two iterators matches what would have been built by this * class if the tokens were passed through it. */ public static bool HasSimilarSpacing(TokenIterator start, TokenIterator end) { // ::Package::Name* array of const*[] // Single spaces only between words, and between words and prior symbols except for leading symbols and package separators TokenIterator iterator = start; bool pastFirstText = false; FundamentalType lastTokenType = FundamentalType.Null; bool dontAddSpaceAfterSymbol = false; bool lastSymbolWasBlock = false; while (iterator < end) { if (iterator.FundamentalType == FundamentalType.Text || iterator.Character == '_') { if (lastTokenType == FundamentalType.Null || lastTokenType == FundamentalType.Text || (lastTokenType == FundamentalType.Symbol && (dontAddSpaceAfterSymbol || (!pastFirstText && !lastSymbolWasBlock))) || lastTokenType == FundamentalType.Whitespace) { pastFirstText = true; lastTokenType = FundamentalType.Text; if (!TryToSkipModifierBlock(ref iterator)) { iterator.Next(); } } else { return(false); } } else if (iterator.FundamentalType == FundamentalType.Symbol) { if (iterator.Character == ',') { // Quit early on commas, since it could be x[,,] or (x, y), in which case it's not clear whether there should be // a space without making this logic even more complicated. Just fail out and build a new one. return(false); } if (lastTokenType == FundamentalType.Null || lastTokenType == FundamentalType.Text || lastTokenType == FundamentalType.Symbol) { lastTokenType = FundamentalType.Symbol; if (iterator.MatchesAcrossTokens("::")) { lastSymbolWasBlock = false; dontAddSpaceAfterSymbol = true; iterator.NextByCharacters(2); } else if (iterator.Character == '.' || iterator.Character == '%' || iterator.Character == '"' || iterator.Character == '\'' || iterator.Character == '@' || iterator.Character == '(') { lastSymbolWasBlock = false; dontAddSpaceAfterSymbol = true; iterator.Next(); } else if (TryToSkipModifierBlock(ref iterator)) { lastSymbolWasBlock = true; dontAddSpaceAfterSymbol = false; // already moved iterator } else { lastSymbolWasBlock = false; dontAddSpaceAfterSymbol = false; iterator.Next(); } } else { return(false); } } else if (iterator.FundamentalType == FundamentalType.Whitespace && iterator.Character == ' ' && iterator.RawTextLength == 1) { if ((lastTokenType == FundamentalType.Symbol && !dontAddSpaceAfterSymbol && (pastFirstText || lastSymbolWasBlock)) || lastTokenType == FundamentalType.Text) { lastTokenType = FundamentalType.Whitespace; iterator.Next(); } else { return(false); } } else { return(false); } } return(true); }
/* Function: HasSimilarSpacing * Returns whether the spacing of the tokens between the two iterators matches what would have been built by this * class if the tokens were passed through it. */ public static bool HasSimilarSpacing(TokenIterator start, TokenIterator end) { // ::Package::Name* array of const*[] // Single spaces only between words, and between words and prior symbols except for leading symbols and package separators TokenIterator iterator = start; bool pastFirstText = false; FundamentalType lastTokenType = FundamentalType.Null; bool dontAddSpaceAfterSymbol = false; bool lastSymbolWasBlock = false; while (iterator < end) { if (iterator.FundamentalType == FundamentalType.Text || iterator.Character == '_') { if (lastTokenType == FundamentalType.Null || lastTokenType == FundamentalType.Text || (lastTokenType == FundamentalType.Symbol && (dontAddSpaceAfterSymbol || (!pastFirstText && !lastSymbolWasBlock))) || lastTokenType == FundamentalType.Whitespace) { pastFirstText = true; lastTokenType = FundamentalType.Text; if (!TryToSkipModifierBlock(ref iterator)) { iterator.Next(); } } else { return(false); } } else if (iterator.FundamentalType == FundamentalType.Symbol) { if (lastTokenType == FundamentalType.Null || lastTokenType == FundamentalType.Text || lastTokenType == FundamentalType.Symbol) { lastTokenType = FundamentalType.Symbol; if (iterator.MatchesAcrossTokens("::")) { lastSymbolWasBlock = false; dontAddSpaceAfterSymbol = true; iterator.NextByCharacters(2); } else if (iterator.Character == '.' || iterator.Character == '%') { lastSymbolWasBlock = false; dontAddSpaceAfterSymbol = true; iterator.Next(); } else if (TryToSkipModifierBlock(ref iterator)) { lastSymbolWasBlock = true; dontAddSpaceAfterSymbol = false; // already moved iterator } else { lastSymbolWasBlock = false; dontAddSpaceAfterSymbol = false; iterator.Next(); } } else { return(false); } } else if (iterator.FundamentalType == FundamentalType.Whitespace && iterator.Character == ' ' && iterator.RawTextLength == 1) { if ((lastTokenType == FundamentalType.Symbol && !dontAddSpaceAfterSymbol && (pastFirstText || lastSymbolWasBlock)) || lastTokenType == FundamentalType.Text) { lastTokenType = FundamentalType.Whitespace; iterator.Next(); } else { return(false); } } else { return(false); } } return(true); }