/* Function: BuildCurrentClass */ protected void BuildCurrentClass() { htmlOutput.Append("<div class=\"CPEntry T" + EngineInstance.CommentTypes.FromID(topic.CommentTypeID).SimpleIdentifier + " Current\">"); // Pre-prototype lines int lineCount = parsedPrototype.NumberOfPrePrototypeLines; TokenIterator start, end; for (int i = 0; i < lineCount; i++) { parsedPrototype.GetPrePrototypeLine(i, out start, out end); htmlOutput.Append("<div class=\"CPPrePrototypeLine\">"); BuildSyntaxHighlightedText(start, end); htmlOutput.Append("</div>"); } // Keyword and modifiers. We only show the keyword if it's not "class". TokenIterator startKeyword, endKeyword; topic.ParsedClassPrototype.GetKeyword(out startKeyword, out endKeyword); string keyword = startKeyword.String; TokenIterator startModifiers, endModifiers; bool hasModifiers = topic.ParsedClassPrototype.GetModifiers(out startModifiers, out endModifiers); if (hasModifiers || keyword != "class") { StringBuilder modifiersOutput = new StringBuilder(); TokenIterator partial; bool hasPartial = startModifiers.Tokenizer.FindTokenBetween("partial", EngineInstance.Languages.FromID(topic.LanguageID).CaseSensitive, startModifiers, endModifiers, out partial); if (hasPartial) { TokenIterator lookahead = partial; lookahead.Next(); if (lookahead < endModifiers && (lookahead.FundamentalType == FundamentalType.Text || lookahead.Character == '_')) { hasPartial = false; } TokenIterator lookbehind = partial; lookbehind.Previous(); if (lookbehind >= startModifiers && (lookbehind.FundamentalType == FundamentalType.Text || lookbehind.Character == '_')) { hasPartial = false; } } if (hasModifiers && hasPartial) { if (partial > startModifiers) { TokenIterator lookbehind = partial; lookbehind.PreviousPastWhitespace(PreviousPastWhitespaceMode.EndingBounds); BuildSyntaxHighlightedText(startModifiers, lookbehind, modifiersOutput); } partial.Next(); partial.NextPastWhitespace(); if (partial < endModifiers) { if (modifiersOutput.Length > 0) { modifiersOutput.Append(' '); } BuildSyntaxHighlightedText(partial, endModifiers, modifiersOutput); } } else if (hasModifiers) { BuildSyntaxHighlightedText(startModifiers, endModifiers, modifiersOutput); } if (keyword != "class") { if (modifiersOutput.Length > 0) { modifiersOutput.Append(' '); } BuildSyntaxHighlightedText(startKeyword, endKeyword, modifiersOutput); } if (modifiersOutput.Length > 0) { htmlOutput.Append("<div class=\"CPModifiers\">"); htmlOutput.Append(modifiersOutput.ToString()); htmlOutput.Append("</div>"); } } // Name. We use the fully resolved name in the symbol instead of the prototype name, which may just be the last segment. htmlOutput.Append("<div class=\"CPName\">"); BuildWrappedTitle(topic.Symbol.FormatWithSeparator(this.language.MemberOperator), topic.CommentTypeID, htmlOutput); TokenIterator startTemplate, endTemplate; if (topic.ParsedClassPrototype.GetTemplateSuffix(out startTemplate, out endTemplate)) { // Include a zero-width space for wrapping htmlOutput.Append("​<span class=\"TemplateSignature\">"); htmlOutput.EntityEncodeAndAppend(startTemplate.Tokenizer.TextBetween(startTemplate, endTemplate)); htmlOutput.Append("</span>"); } htmlOutput.Append("</div>"); // Post-prototype lines lineCount = parsedPrototype.NumberOfPostPrototypeLines; for (int i = 0; i < lineCount; i++) { parsedPrototype.GetPostPrototypeLine(i, out start, out end); htmlOutput.Append("<div class=\"CPPostPrototypeLine\">"); BuildSyntaxHighlightedText(start, end); htmlOutput.Append("</div>"); } htmlOutput.Append("</div>"); }
/* Function: TryToSkipAnnotationParameters * * Tries to move the iterator past an annotation parameter section, such as "("String")" in "@Copynight("String")". * * Supported Modes: * * - <ParseMode.IterateOnly> * - <ParseMode.SyntaxHighlight> * - <ParseMode.ParsePrototype> * - The contents will be marked with parameter tokens. * - Everything else is treated as <ParseMode.IterateOnly>. */ protected bool TryToSkipAnnotationParameters(ref TokenIterator iterator, ParseMode mode = ParseMode.IterateOnly) { if (iterator.Character != '(') { return(false); } TokenIterator lookahead = iterator; if (!TryToSkipBlock(ref lookahead, false)) { return(false); } TokenIterator end = lookahead; if (mode == ParseMode.SyntaxHighlight) { iterator.SetSyntaxHighlightingTypeBetween(end, SyntaxHighlightingType.Metadata); } else if (mode == ParseMode.ParsePrototype) { TokenIterator openingParen = iterator; TokenIterator closingParen = lookahead; closingParen.Previous(); openingParen.PrototypeParsingType = PrototypeParsingType.StartOfParams; closingParen.PrototypeParsingType = PrototypeParsingType.EndOfParams; lookahead = openingParen; lookahead.Next(); TokenIterator startOfParam = lookahead; while (lookahead < closingParen) { if (lookahead.Character == ',') { MarkAnnotationParameter(startOfParam, lookahead, mode); lookahead.PrototypeParsingType = PrototypeParsingType.ParamSeparator; lookahead.Next(); startOfParam = lookahead; } else if (TryToSkipComment(ref lookahead) || TryToSkipString(ref lookahead) || TryToSkipBlock(ref lookahead, true)) { } else { lookahead.Next(); } } MarkAnnotationParameter(startOfParam, lookahead, mode); } iterator = end; return(true); }
// Group: Support Functions // __________________________________________________________________________ /* Function: CalculateParameterTable * Fills in <parameterTableTokenIndexes>, <parameterTableColumnUsed>, and <symbolColumnWidth> for the * passed section. */ protected void CalculateParameterTable(Prototypes.ParameterSection section) { // // Check if this is a raw section with nothing other than parameter separators, meaning no name, type, etc. tokens // TokenIterator iterator = section.Start; TokenIterator endOfSection = section.End; bool isRaw = true; while (iterator < endOfSection) { var type = iterator.PrototypeParsingType; if (type != PrototypeParsingType.Null && type != PrototypeParsingType.StartOfPrototypeSection && type != PrototypeParsingType.StartOfParams && type != PrototypeParsingType.ParamSeparator && type != PrototypeParsingType.EndOfParams && type != PrototypeParsingType.EndOfPrototypeSection) { isRaw = false; break; } iterator.Next(); } // // Now fill in parameterTableTokenIndexes // parameterTableTokenIndexes = new int[section.NumberOfParameters, NumberOfColumns + 1]; for (int parameterIndex = 0; parameterIndex < section.NumberOfParameters; parameterIndex++) { TokenIterator startOfParam, endOfParam; section.GetParameterBounds(parameterIndex, out startOfParam, out endOfParam); iterator = startOfParam; iterator.NextPastWhitespace(endOfParam); // C-Style Parameters if (section.ParameterStyle == ParsedPrototype.ParameterStyle.C) { while (iterator < endOfParam && iterator.PrototypeParsingType == PrototypeParsingType.Null && iterator.FundamentalType == FundamentalType.Whitespace) { iterator.Next(); } // ModifierQualifier int currentColumn = 0; parameterTableTokenIndexes[parameterIndex, currentColumn] = iterator.TokenIndex; TokenIterator startOfType = iterator; if (!isRaw) { while (iterator < endOfParam) { PrototypeParsingType type = iterator.PrototypeParsingType; // Null covers whitespace and any random symbols we encountered that went unmarked. if (type == PrototypeParsingType.TypeModifier || type == PrototypeParsingType.TypeQualifier || type == PrototypeParsingType.ParamModifier || type == PrototypeParsingType.Null) { iterator.Next(); } else if (type == PrototypeParsingType.OpeningTypeModifier || type == PrototypeParsingType.OpeningParamModifier) { SkipModifierBlock(ref iterator, endOfParam); } else { break; } } } // Type currentColumn++; parameterTableTokenIndexes[parameterIndex, currentColumn] = iterator.TokenIndex; // Allow this column to claim the contents of a raw prototype. They should all be null tokens. // We use the type column instead of the name column because the name column isn't fully syntax highlighted. while (iterator < endOfParam) { PrototypeParsingType type = iterator.PrototypeParsingType; // The previous loop already got any modifiers before the type, so this will only cover the type // plus any modifiers following it. if (type == PrototypeParsingType.Type || type == PrototypeParsingType.TypeModifier || type == PrototypeParsingType.ParamModifier || (isRaw && type == PrototypeParsingType.ParamSeparator) || type == PrototypeParsingType.Null) { iterator.Next(); } else if (type == PrototypeParsingType.OpeningTypeModifier || type == PrototypeParsingType.OpeningParamModifier) { SkipModifierBlock(ref iterator, endOfParam); } else { break; } } // Symbols currentColumn++; parameterTableTokenIndexes[parameterIndex, currentColumn] = iterator.TokenIndex; if (!isRaw) { // All symbols are part of the type column right now because they're marked as type or param // modifiers. Walk backwards to claim the symbols from the type column. if (iterator > startOfType) { TokenIterator lookbehind = iterator; lookbehind.Previous(); if (lookbehind.FundamentalType == FundamentalType.Symbol && lookbehind.Character != '_' && lookbehind.PrototypeParsingType != PrototypeParsingType.ClosingTypeModifier && lookbehind.PrototypeParsingType != PrototypeParsingType.ClosingParamModifier) { parameterTableTokenIndexes[parameterIndex, currentColumn] = lookbehind.TokenIndex; lookbehind.Previous(); while (lookbehind >= startOfType) { if (lookbehind.FundamentalType == FundamentalType.Symbol && lookbehind.Character != '_' && lookbehind.PrototypeParsingType != PrototypeParsingType.ClosingTypeModifier && lookbehind.PrototypeParsingType != PrototypeParsingType.ClosingParamModifier) { parameterTableTokenIndexes[parameterIndex, currentColumn] = lookbehind.TokenIndex; lookbehind.Previous(); } else { break; } } // Fix up any columns we stole from for (int i = 0; i < currentColumn; i++) { if (parameterTableTokenIndexes[parameterIndex, i] > parameterTableTokenIndexes[parameterIndex, currentColumn]) { parameterTableTokenIndexes[parameterIndex, i] = parameterTableTokenIndexes[parameterIndex, currentColumn]; } } } } } // Name currentColumn++; parameterTableTokenIndexes[parameterIndex, currentColumn] = iterator.TokenIndex; if (!isRaw) { while (iterator < endOfParam) { PrototypeParsingType type = iterator.PrototypeParsingType; // Include the parameter separator because there may not be a default value. // Include modifiers because there still may be some after the name. if (type == PrototypeParsingType.Name || type == PrototypeParsingType.TypeModifier || type == PrototypeParsingType.ParamModifier || type == PrototypeParsingType.ParamSeparator || type == PrototypeParsingType.Null) { iterator.Next(); } else if (type == PrototypeParsingType.OpeningTypeModifier || type == PrototypeParsingType.OpeningParamModifier) { SkipModifierBlock(ref iterator, endOfParam); } else { break; } } } // PropertyValueSeparator currentColumn++; parameterTableTokenIndexes[parameterIndex, currentColumn] = iterator.TokenIndex; if (!isRaw) { while (iterator < endOfParam) { PrototypeParsingType type = iterator.PrototypeParsingType; if (type == PrototypeParsingType.PropertyValueSeparator || type == PrototypeParsingType.Null) { iterator.Next(); } else { break; } } } // PropertyValue currentColumn++; parameterTableTokenIndexes[parameterIndex, currentColumn] = iterator.TokenIndex; if (!isRaw) { while (iterator < endOfParam) { PrototypeParsingType type = iterator.PrototypeParsingType; if (type == PrototypeParsingType.PropertyValue || type == PrototypeParsingType.ParamSeparator || type == PrototypeParsingType.Null) { iterator.Next(); } else { break; } } } // DefaultValueSeparator currentColumn++; parameterTableTokenIndexes[parameterIndex, currentColumn] = iterator.TokenIndex; if (!isRaw) { while (iterator < endOfParam) { PrototypeParsingType type = iterator.PrototypeParsingType; if (type == PrototypeParsingType.DefaultValueSeparator || type == PrototypeParsingType.Null) { iterator.Next(); } else { break; } } } // DefaultValue currentColumn++; parameterTableTokenIndexes[parameterIndex, currentColumn] = iterator.TokenIndex; // End of param currentColumn++; parameterTableTokenIndexes[parameterIndex, currentColumn] = endOfParam.TokenIndex; } // Pascal-Style Parameters else if (section.ParameterStyle == ParsedPrototype.ParameterStyle.Pascal) { while (iterator < endOfParam && iterator.PrototypeParsingType == PrototypeParsingType.Null && iterator.FundamentalType == FundamentalType.Whitespace) { iterator.Next(); } // ModifierQualifier int currentColumn = 0; parameterTableTokenIndexes[parameterIndex, currentColumn] = iterator.TokenIndex; if (!isRaw) { while (iterator < endOfParam) { PrototypeParsingType type = iterator.PrototypeParsingType; if (type == PrototypeParsingType.TypeModifier || type == PrototypeParsingType.ParamModifier || type == PrototypeParsingType.ParamSeparator || type == PrototypeParsingType.Null) { iterator.Next(); } else if (type == PrototypeParsingType.OpeningTypeModifier || type == PrototypeParsingType.OpeningParamModifier) { SkipModifierBlock(ref iterator, endOfParam); } else { break; } } } // Do we have a name-type separator? We may not, such as for SQL. bool hasNameTypeSeparator = false; TokenIterator lookahead = iterator; if (!isRaw) { while (lookahead < endOfParam) { if (lookahead.PrototypeParsingType == PrototypeParsingType.NameTypeSeparator) { hasNameTypeSeparator = true; break; } lookahead.Next(); } } // Name currentColumn++; parameterTableTokenIndexes[parameterIndex, currentColumn] = iterator.TokenIndex; if (!isRaw) { while (iterator < endOfParam) { PrototypeParsingType type = iterator.PrototypeParsingType; // Include the parameter separator because there may not be a type. if (type == PrototypeParsingType.Name || type == PrototypeParsingType.ParamSeparator || type == PrototypeParsingType.Null) { iterator.Next(); } // Include modifiers because there still may be some after the name, but only if there's a name-type separator. else if (hasNameTypeSeparator && (type == PrototypeParsingType.TypeModifier || type == PrototypeParsingType.ParamModifier)) { iterator.Next(); } else if (type == PrototypeParsingType.OpeningTypeModifier || type == PrototypeParsingType.OpeningParamModifier) { SkipModifierBlock(ref iterator, endOfParam); } else { break; } } } // TypeNameSeparator currentColumn++; parameterTableTokenIndexes[parameterIndex, currentColumn] = iterator.TokenIndex; if (!isRaw) { while (iterator < endOfParam) { PrototypeParsingType type = iterator.PrototypeParsingType; if (type == PrototypeParsingType.NameTypeSeparator || type == PrototypeParsingType.Null) { iterator.Next(); } else { break; } } } // Symbols currentColumn++; parameterTableTokenIndexes[parameterIndex, currentColumn] = iterator.TokenIndex; if (!isRaw) { if (iterator < endOfParam && iterator.FundamentalType == FundamentalType.Symbol && iterator.Character != '_') { while (iterator < endOfParam) { PrototypeParsingType type = iterator.PrototypeParsingType; if (( (iterator.FundamentalType == FundamentalType.Symbol && iterator.Character != '_') || (iterator.FundamentalType == FundamentalType.Whitespace) ) && (type == PrototypeParsingType.TypeModifier || type == PrototypeParsingType.ParamModifier || type == PrototypeParsingType.Null)) { iterator.Next(); } else { break; } } } } // Type currentColumn++; parameterTableTokenIndexes[parameterIndex, currentColumn] = iterator.TokenIndex; // Allow this column to claim the contents of a raw prototype. They should all be null tokens. // We use the type column instead of the name column because the name column isn't syntax highlighted. while (iterator < endOfParam) { PrototypeParsingType type = iterator.PrototypeParsingType; // Include the parameter separator because there may not be a default value. if (type == PrototypeParsingType.Type || type == PrototypeParsingType.TypeModifier || type == PrototypeParsingType.TypeQualifier || type == PrototypeParsingType.ParamModifier || type == PrototypeParsingType.ParamSeparator || type == PrototypeParsingType.Null) { iterator.Next(); } else if (type == PrototypeParsingType.OpeningTypeModifier || type == PrototypeParsingType.OpeningParamModifier) { SkipModifierBlock(ref iterator, endOfParam); } else { break; } } // PropertyValueSeparator currentColumn++; parameterTableTokenIndexes[parameterIndex, currentColumn] = iterator.TokenIndex; if (!isRaw) { while (iterator < endOfParam) { PrototypeParsingType type = iterator.PrototypeParsingType; if (type == PrototypeParsingType.PropertyValueSeparator || type == PrototypeParsingType.Null) { iterator.Next(); } else { break; } } } // PropertyValue currentColumn++; parameterTableTokenIndexes[parameterIndex, currentColumn] = iterator.TokenIndex; if (!isRaw) { while (iterator < endOfParam) { PrototypeParsingType type = iterator.PrototypeParsingType; if (type == PrototypeParsingType.PropertyValue || type == PrototypeParsingType.ParamSeparator || type == PrototypeParsingType.Null) { iterator.Next(); } else { break; } } } // DefaultValueSeparator currentColumn++; parameterTableTokenIndexes[parameterIndex, currentColumn] = iterator.TokenIndex; if (!isRaw) { while (iterator < endOfParam) { PrototypeParsingType type = iterator.PrototypeParsingType; if (type == PrototypeParsingType.DefaultValueSeparator || type == PrototypeParsingType.Null) { iterator.Next(); } else { break; } } } // DefaultValue currentColumn++; parameterTableTokenIndexes[parameterIndex, currentColumn] = iterator.TokenIndex; // End of param currentColumn++; parameterTableTokenIndexes[parameterIndex, currentColumn] = endOfParam.TokenIndex; } } // // Next fill in parameterTableColumnsUsed // // There's a very high likelihood of this array always being the same length so it's worth it to try to reuse the // memory and avoid a reallocation. if (parameterTableColumnsUsed != null && parameterTableColumnsUsed.Length == NumberOfColumns) { Array.Clear(parameterTableColumnsUsed, 0, NumberOfColumns); } else { parameterTableColumnsUsed = new bool[NumberOfColumns]; } for (int parameterIndex = 0; parameterIndex < section.NumberOfParameters; parameterIndex++) { for (int columnIndex = 0; columnIndex < NumberOfColumns; columnIndex++) { if (parameterTableTokenIndexes[parameterIndex, columnIndex] != parameterTableTokenIndexes[parameterIndex, columnIndex + 1]) { parameterTableColumnsUsed[columnIndex] = true; } } } // // Next determine the symbol column's width // symbolColumnWidth = 0; if (parameterTableColumnsUsed[SymbolsColumnIndex]) { for (int parameterIndex = 0; parameterIndex < section.NumberOfParameters; parameterIndex++) { int startTokenIndex = parameterTableTokenIndexes[parameterIndex, SymbolsColumnIndex]; int endTokenIndex = parameterTableTokenIndexes[parameterIndex, SymbolsColumnIndex + 1]; if (endTokenIndex > startTokenIndex) { TokenIterator start, end; section.GetParameterBounds(parameterIndex, out start, out end); start.Next(startTokenIndex - start.TokenIndex); end.Previous(end.TokenIndex - endTokenIndex); int paramColumnWidth = end.RawTextIndex - start.RawTextIndex; if (paramColumnWidth > symbolColumnWidth) { symbolColumnWidth = paramColumnWidth; } } } } }
// Group: Parsing Functions // __________________________________________________________________________ /* Function: TryToSkipPODLine * If the iterator is on a POD line such as "=pod", moves the iterator past it and returns true and the type. If the iterator * isn't on a POD line it will return false and leave the iterator alone. */ protected bool TryToSkipPODLine(ref TokenIterator iterator, out PODLineType type) { type = 0; if (iterator.Character != '=') { return(false); } TokenIterator lookbehind = iterator; for (;;) { lookbehind.Previous(); if (lookbehind.IsInBounds == false || lookbehind.FundamentalType == FundamentalType.LineBreak) { break; } else if (lookbehind.FundamentalType != FundamentalType.Whitespace) { return(false); } } TokenIterator endOfLine = iterator; endOfLine.Next(); if (endOfLine.FundamentalType != FundamentalType.Text) { return(false); } do { endOfLine.Next(); }while (endOfLine.IsInBounds && endOfLine.FundamentalType != FundamentalType.LineBreak); TokenIterator endOfContent = endOfLine; endOfContent.PreviousPastWhitespace(PreviousPastWhitespaceMode.EndingBounds); // ND and Javadoc lines may also match PODBeginLineRegex so test them first. if (iterator.Tokenizer.MatchTextBetween(PODBeginNDLineRegex, iterator, endOfContent).Success) { type = PODLineType.StartNaturalDocs; } else if (iterator.Tokenizer.MatchTextBetween(PODBeginJavadocLineRegex, iterator, endOfContent).Success) { type = PODLineType.StartJavadoc; } else if (iterator.Tokenizer.MatchTextBetween(PODBeginLineRegex, iterator, endOfContent).Success) { type = PODLineType.StartPOD; } else if (iterator.Tokenizer.MatchTextBetween(PODEndLineRegex, iterator, endOfContent).Success) { type = PODLineType.End; } else { return(false); } iterator = endOfLine; if (iterator.FundamentalType == FundamentalType.LineBreak) { iterator.Next(); } return(true); }
/* Function: AppendCurrentClassPrototype */ protected void AppendCurrentClassPrototype(StringBuilder output) { // Main tag string simpleTypeIdentifier = EngineInstance.CommentTypes.FromID(context.Topic.CommentTypeID).SimpleIdentifier; output.Append("<div class=\"CPEntry T" + simpleTypeIdentifier + " Current\">"); // Pre-prototype lines int lineCount = parsedClassPrototype.NumberOfPrePrototypeLines; TokenIterator start, end; for (int i = 0; i < lineCount; i++) { parsedClassPrototype.GetPrePrototypeLine(i, out start, out end); output.Append("<div class=\"CPPrePrototypeLine\">"); AppendSyntaxHighlightedText(start, end, output); output.Append("</div>"); } // Keyword and modifiers. We only show the keyword if it's not "class", and we exclude "partial" from the list of modifiers. TokenIterator startKeyword, endKeyword; parsedClassPrototype.GetKeyword(out startKeyword, out endKeyword); string keyword = startKeyword.String; TokenIterator startModifiers, endModifiers; bool hasModifiers = parsedClassPrototype.GetModifiers(out startModifiers, out endModifiers); if (hasModifiers || keyword != "class") { StringBuilder modifiersOutput = new StringBuilder(); TokenIterator partial; bool hasPartial = startModifiers.Tokenizer.FindTokenBetween("partial", language.CaseSensitive, startModifiers, endModifiers, out partial); // Make sure "partial" is a keyword and not part of a longer identifier if (hasPartial) { TokenIterator lookahead = partial; lookahead.Next(); if (lookahead < endModifiers && (lookahead.FundamentalType == FundamentalType.Text || lookahead.Character == '_')) { hasPartial = false; } TokenIterator lookbehind = partial; lookbehind.Previous(); if (lookbehind >= startModifiers && (lookbehind.FundamentalType == FundamentalType.Text || lookbehind.Character == '_')) { hasPartial = false; } } // Add the modifiers sans-"partial" if (hasModifiers && hasPartial) { if (partial > startModifiers) { TokenIterator lookbehind = partial; lookbehind.PreviousPastWhitespace(PreviousPastWhitespaceMode.EndingBounds); AppendSyntaxHighlightedText(startModifiers, lookbehind, modifiersOutput); } partial.Next(); partial.NextPastWhitespace(); if (partial < endModifiers) { if (modifiersOutput.Length > 0) { modifiersOutput.Append(' '); } AppendSyntaxHighlightedText(partial, endModifiers, modifiersOutput); } } else if (hasModifiers) { AppendSyntaxHighlightedText(startModifiers, endModifiers, modifiersOutput); } // Add the keyword if it isn't "class" if (keyword != "class") { if (modifiersOutput.Length > 0) { modifiersOutput.Append(' '); } AppendSyntaxHighlightedText(startKeyword, endKeyword, modifiersOutput); } if (modifiersOutput.Length > 0) { output.Append("<div class=\"CPModifiers\">"); output.Append(modifiersOutput.ToString()); output.Append("</div>"); } } // Name. We use the fully resolved name in the symbol instead of the prototype name, which may just be the last segment. output.Append("<div class=\"CPName\">"); AppendWrappedTitle(context.Topic.Symbol.FormatWithSeparator(language.MemberOperator), WrappedTitleMode.Code, output); TokenIterator startTemplate, endTemplate; if (parsedClassPrototype.GetTemplateSuffix(out startTemplate, out endTemplate)) { // Include preceding whitespace if present, or a zero-width space for wrapping if not if (!startTemplate.PreviousPastWhitespace(PreviousPastWhitespaceMode.EndingBounds)) { output.Append("​"); } output.Append("<span class=\"TemplateSignature\">"); AppendSyntaxHighlightedText(startTemplate, endTemplate, output); output.Append("</span>"); } output.Append("</div>"); // Post-prototype lines lineCount = parsedClassPrototype.NumberOfPostPrototypeLines; for (int i = 0; i < lineCount; i++) { parsedClassPrototype.GetPostPrototypeLine(i, out start, out end); output.Append("<div class=\"CPPostPrototypeLine\">"); AppendSyntaxHighlightedText(start, end, output); output.Append("</div>"); } output.Append("</div>"); }
/* Function: AppendSyntaxHighlightedTextWithTypeLinks * * Formats the text between the iterators with syntax highlighting and links for any tokens marked with * <PrototypeParsingType.Type> and <PrototypeParsingType.TypeQualifier>. Appends the result to the passed StringBuilder. * * Parameters: * * start - The first token of the text to convert. * end - The end of the text to convert, which is one token past the last one included. * output - The StringBuilder to append the output to. * * links - A list of <Links> that should contain any appearing in the code. * linkTargets - A list of topics that should contain any used as targets in the list of links. * * extendTypeSearch - If true, it will search beyond the bounds of the iterators to get the complete type. This allows you to * format only a portion of the link with this function yet still have the link go to the complete destination. * * Requirements: * * - The <Context>'s topic and page must be set. */ public void AppendSyntaxHighlightedTextWithTypeLinks(TokenIterator start, TokenIterator end, StringBuilder output, IList <Link> links, IList <Topics.Topic> linkTargets, bool extendTypeSearch = false) { #if DEBUG if (Context.Topic == null) { throw new Exception("Tried to call AppendSyntaxtHighlightedTextWithTypeLinks without setting the context's topic."); } if (Context.Page.IsNull) { throw new Exception("Tried to call AppendSyntaxtHighlightedTextWithTypeLinks without setting the context's page."); } if (links == null) { throw new Exception("Tried to call AppendSyntaxtHighlightedTextWithTypeLinks without setting the links variable."); } if (linkTargets == null) { throw new Exception("Tried to call AppendSyntaxtHighlightedTextWithTypeLinks without setting the linkTargets variable."); } #endif Language language = EngineInstance.Languages.FromID(Context.Topic.LanguageID); // Find each Type/TypeQualifier stretch in the text TokenIterator iterator = start; while (iterator < end) { if (iterator.PrototypeParsingType == PrototypeParsingType.Type || iterator.PrototypeParsingType == PrototypeParsingType.TypeQualifier) { TokenIterator textStart = iterator; TokenIterator textEnd = iterator; do { textEnd.Next(); }while (textEnd < end && (textEnd.PrototypeParsingType == PrototypeParsingType.Type || textEnd.PrototypeParsingType == PrototypeParsingType.TypeQualifier)); TokenIterator symbolStart = textStart; TokenIterator symbolEnd = textEnd; // Extend past start and end if the flag is set if (extendTypeSearch && symbolStart == start) { TokenIterator temp = symbolStart; temp.Previous(); while (temp.IsInBounds && (temp.PrototypeParsingType == PrototypeParsingType.Type || temp.PrototypeParsingType == PrototypeParsingType.TypeQualifier)) { symbolStart = temp; temp.Previous(); } } if (extendTypeSearch && symbolEnd == end) { while (symbolEnd.IsInBounds && (symbolEnd.PrototypeParsingType == PrototypeParsingType.Type || symbolEnd.PrototypeParsingType == PrototypeParsingType.TypeQualifier)) { symbolEnd.Next(); } } // Built in types don't get links if (language.IsBuiltInType(symbolStart, symbolEnd)) { AppendSyntaxHighlightedText(textStart, textEnd, output); } else { // Create a link object with the identifying properties needed to look it up in the list of links. Link linkStub = new Link(); linkStub.Type = LinkType.Type; linkStub.Symbol = SymbolString.FromPlainText_NoParameters(symbolStart.TextBetween(symbolEnd)); linkStub.Context = Context.Topic.PrototypeContext; linkStub.ContextID = Context.Topic.PrototypeContextID; linkStub.FileID = Context.Topic.FileID; linkStub.ClassString = Context.Topic.ClassString; linkStub.ClassID = Context.Topic.ClassID; linkStub.LanguageID = Context.Topic.LanguageID; // Find the actual link so we know if it resolved to anything. Link fullLink = null; foreach (Link link in links) { if (link.SameIdentifyingPropertiesAs(linkStub)) { fullLink = link; break; } } #if DEBUG if (fullLink == null) { throw new Exception("All links in a topic must be in the list passed to AppendSyntaxtHighlightedTextWithTypeLinks."); } #endif // If it didn't resolve, we just output the original text. if (!fullLink.IsResolved) { AppendSyntaxHighlightedText(textStart, textEnd, output); } else { // If it did resolve, find Topic it resolved to. Topics.Topic targetTopic = null; foreach (var linkTarget in linkTargets) { if (linkTarget.TopicID == fullLink.TargetTopicID) { targetTopic = linkTarget; break; } } #if DEBUG if (targetTopic == null) { throw new Exception("All links targets for a topic must be in the list passed to AppendSyntaxtHighlightedTextWithTypeLinks."); } #endif AppendOpeningLinkTag(targetTopic, output); AppendSyntaxHighlightedText(textStart, textEnd, output); output.Append("</a>"); } } iterator = textEnd; } else // not on a type { TokenIterator startText = iterator; do { iterator.Next(); }while (iterator < end && iterator.PrototypeParsingType != PrototypeParsingType.Type && iterator.PrototypeParsingType != PrototypeParsingType.TypeQualifier); AppendSyntaxHighlightedText(startText, iterator, output); } } }
// Group: Parameter Functions // __________________________________________________________________________ /* Function: RecalculateParameters * Scans this section for <PrototypeParsingType.StartOfParams>, <PrototypeParsingType.EndOfParams>, and * <PrototypeParsingTypes.ParamSeparator> tokens to determine how many parameters there are and allow easy access to them * individually. This is automatically called by the constructor so you only need to call this manually if you made changes to these * token types after creating this object. */ public void RecalculateParameters() { TokenIterator iterator = start; // Before parameters while (iterator < end && iterator.PrototypeParsingType != PrototypeParsingType.StartOfParams) { iterator.Next(); } if (iterator.PrototypeParsingType == PrototypeParsingType.StartOfParams) { iterator.Next(); } beforeParameters = new Section(start, iterator); // Only trim whitespace if it's insignificant if (iterator.FundamentalType == FundamentalType.Whitespace && iterator.PrototypeParsingType == PrototypeParsingType.Null) { iterator.Next(); } // Count the parameters so we don't allocate more memory than we need for the list. Also, there may not be any parameters // at all since it could be a pair of empty parentheses. if (iterator < end && iterator.PrototypeParsingType != PrototypeParsingType.EndOfParams) { TokenIterator startOfFirstParam = iterator; int paramCount = 1; do { if (iterator.PrototypeParsingType == PrototypeParsingType.ParamSeparator) { paramCount++; } iterator.Next(); }while (iterator < end && iterator.PrototypeParsingType != PrototypeParsingType.EndOfParams); // Get the actual parameters parameters = new List <Section>(paramCount); iterator = startOfFirstParam; TokenIterator startOfParam = iterator; for (;;) { if (iterator >= end || iterator.PrototypeParsingType == PrototypeParsingType.EndOfParams) { TokenIterator lookbehind = iterator; lookbehind.Previous(); if (lookbehind.FundamentalType == FundamentalType.Whitespace && lookbehind.PrototypeParsingType == PrototypeParsingType.Null && lookbehind >= startOfParam) { parameters.Add(new Section(startOfParam, lookbehind)); } else { parameters.Add(new Section(startOfParam, iterator)); } break; } else if (iterator.PrototypeParsingType == PrototypeParsingType.ParamSeparator) { iterator.Next(); parameters.Add(new Section(startOfParam, iterator)); if (iterator.FundamentalType == FundamentalType.Whitespace && iterator.PrototypeParsingType == PrototypeParsingType.Null) { iterator.Next(); } startOfParam = iterator; } else { iterator.Next(); } } } // After parameters if (iterator < end) { afterParameters = new Section(iterator, end); } // Parameter style // Set to null so it recalculates on next use rather than doing it preemptively parameterStyle = null; }
/* Function: GetJavadocLinkSymbol * Returns the symbol part of a @see or {@link} tag and moves the iterator past it. */ protected string GetJavadocLinkSymbol(ref TokenIterator iterator) { StringBuilder symbol = new StringBuilder(); // In Javadoc, spaces are only allowed in parentheses. We're going to go further and allow them in any braces to support // templates and other languages. However, for angle brackets they must be preceded by a comma. This allows // "Template<A, B>" to be supported while not getting tripped on "operator<". // Most symbols won't have braces so create this on demand. SafeStack <char> braceStack = null; while (iterator.IsInBounds) { if (iterator.Character == '(' || iterator.Character == '[' || iterator.Character == '{' || iterator.Character == '<') { if (braceStack == null) { braceStack = new SafeStack <char>(); } braceStack.Push(iterator.Character); symbol.Append(iterator.Character); } else if ((iterator.Character == ')' && braceStack != null && braceStack.Peek() == '(') || (iterator.Character == ']' && braceStack != null && braceStack.Peek() == '[') || (iterator.Character == '}' && braceStack != null && braceStack.Peek() == '{') || (iterator.Character == '>' && braceStack != null && braceStack.Peek() == '<')) { braceStack.Pop(); symbol.Append(iterator.Character); } else if (iterator.Character == '}' && (braceStack == null || braceStack.Contains('{') == false)) { // If we're at an unopened closing brace we're probably at the end of a {@link}. We check if the stack contains an // opening brace instead of checking whether it's empty to ignore any possible opening angle brackets that could // screw us up like "operator<". break; } else if (iterator.FundamentalType == FundamentalType.Text || iterator.FundamentalType == FundamentalType.Symbol) { iterator.AppendTokenTo(symbol); } else if (iterator.FundamentalType == FundamentalType.Whitespace) { if (braceStack == null || braceStack.Count == 0) { break; } else if (braceStack.Peek() == '<') { TokenIterator lookbehind = iterator; lookbehind.Previous(); if (lookbehind.Character == ',') { iterator.AppendTokenTo(symbol); } else { break; } } else { iterator.AppendTokenTo(symbol); } } else // line break { break; } iterator.Next(); } // Javadoc uses Class.Class#Member. First remove leading hashes to handle just #Member while (symbol.Length > 0 && symbol[0] == '#') { symbol.Remove(0, 1); } // Convert any remaining hashes to dots. Ideally we would use the language's native member operator but it's not // easy to get it here. return(symbol.ToString().Replace('#', '.')); }
/* Function: ParsePrototype * Converts a raw text prototype into a <ParsedPrototype>. */ override public ParsedPrototype ParsePrototype(string stringPrototype, int commentTypeID) { Tokenizer tokenizedPrototype = new Tokenizer(stringPrototype, tabWidth: EngineInstance.Config.TabWidth); ParsedPrototype parsedPrototype; // Mark any leading decorators. TokenIterator iterator = tokenizedPrototype.FirstToken; TryToSkipWhitespace(ref iterator, true, ParseMode.ParsePrototype); if (TryToSkipDecorators(ref iterator, ParseMode.ParsePrototype)) { TryToSkipWhitespace(ref iterator, true, ParseMode.ParsePrototype); } // Search for the first opening bracket or brace. char closingBracket = '\0'; while (iterator.IsInBounds) { if (iterator.Character == '(') { closingBracket = ')'; break; } else if (iterator.Character == '[') { // Only treat brackets as parameters if it's following "this", meaning it's an iterator. Ignore all others so we // don't get tripped up on metadata or array brackets on return values. TokenIterator lookbehind = iterator; lookbehind.Previous(); lookbehind.PreviousPastWhitespace(PreviousPastWhitespaceMode.Iterator); if (lookbehind.MatchesToken("this")) { closingBracket = ']'; break; } else { iterator.Next(); } } else if (iterator.Character == '{') { closingBracket = '}'; break; } else if (TryToSkipComment(ref iterator) || TryToSkipString(ref iterator)) { } else { iterator.Next(); } } // If we found brackets, it's either a function prototype or a class prototype that includes members. // Mark the delimiters. if (closingBracket != '\0') { iterator.PrototypeParsingType = PrototypeParsingType.StartOfParams; iterator.Next(); while (iterator.IsInBounds) { if (iterator.Character == ',') { iterator.PrototypeParsingType = PrototypeParsingType.ParamSeparator; iterator.Next(); } else if (iterator.Character == closingBracket) { iterator.PrototypeParsingType = PrototypeParsingType.EndOfParams; break; } // Unlike prototype detection, here we treat < as an opening bracket. Since we're already in the parameter list // we shouldn't run into it as part of an operator overload, and we need it to not treat the comma in "template<a,b>" // as a parameter divider. else if (TryToSkipComment(ref iterator) || TryToSkipString(ref iterator) || TryToSkipBlock(ref iterator, true)) { } else { iterator.Next(); } } // We have enough tokens marked to create the parsed prototype. This will also let us iterate through the parameters // easily. parsedPrototype = new ParsedPrototype(tokenizedPrototype, supportsImpliedTypes: false); // Set the main section to the last one, since any decorators present will each be in their own section. Some can have // parameter lists and we don't want those confused for the actual parameter list. parsedPrototype.MainSectionIndex = parsedPrototype.Sections.Count - 1; // Mark the part before the parameters, which includes the name and return value. TokenIterator start, end; parsedPrototype.GetBeforeParameters(out start, out end); // Exclude the opening bracket end.Previous(); end.PreviousPastWhitespace(PreviousPastWhitespaceMode.EndingBounds, start); if (start < end) { MarkPascalParameter(start, end); } // If there are any parameters, mark the tokens in them. if (parsedPrototype.NumberOfParameters > 0) { for (int i = 0; i < parsedPrototype.NumberOfParameters; i++) { parsedPrototype.GetParameter(i, out start, out end); MarkPascalParameter(start, end); } } } // If there's no brackets, it's a variable, property, or class. else { parsedPrototype = new ParsedPrototype(tokenizedPrototype); TokenIterator start = tokenizedPrototype.FirstToken; TokenIterator end = tokenizedPrototype.LastToken; MarkPascalParameter(start, end); } return(parsedPrototype); }
/* Function: TryToGetBlockComment * * If the iterator is on a line that starts with the opening symbol of a block comment, 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> and mark the symbols as <CommentParsingType.CommentSymbol>. If the * line does not start with an opening comment symbol it will return false and leave the iterator where it is. */ protected bool TryToGetBlockComment(ref LineIterator lineIterator, out PossibleDocumentationComment comment) { TokenIterator firstToken, endOfLine; lineIterator.GetBounds(LineBoundsMode.ExcludeWhitespace, out firstToken, out endOfLine); // Are we on a block comment? TokenIterator lookahead = firstToken; string closingSymbol; if (TryToSkipOpeningBlockCommentSymbol(ref lookahead, out closingSymbol) == false) { comment = null; return(false); } // We are. Create a possible documentation comment. comment = new PossibleDocumentationComment(); comment.Start = lineIterator; // Check if we're on a Javadoc comment, which will be an extra [. if (lookahead.Character == '[') { lookahead.Next(); if (lookahead.FundamentalType != FundamentalType.Symbol) { comment.Javadoc = true; } } // Find the end of the comment, which could be on the same line as the start. var tokenizer = lineIterator.Tokenizer; var lineLookahead = lineIterator; bool hadTrailingDashes = false; for (;;) { TokenIterator closingSymbolIterator; if (tokenizer.FindTokensBetween(closingSymbol, false, firstToken, endOfLine, out closingSymbolIterator) == true) { // Move past the end of the comment regardless of whether it's acceptable for documentation or not lineLookahead.Next(); // Make sure nothing appears after the closing symbol on the line closingSymbolIterator.NextByCharacters(closingSymbol.Length); // We'll allow -- though since some people use --[[ and ]]-- for balance even though the latter is actually the // closing comment symbol followed by a line comment. if (closingSymbolIterator.MatchesAcrossTokens("--")) { hadTrailingDashes = true; closingSymbolIterator.Next(2); } closingSymbolIterator.NextPastWhitespace(); if (closingSymbolIterator.FundamentalType != FundamentalType.LineBreak && closingSymbolIterator.FundamentalType != FundamentalType.Null) { comment = null; } else { comment.End = lineLookahead; } break; } lineLookahead.Next(); // If we're not in bounds that means there was an unclosed comment at the end of the file. Skip it but don't treat // it as a documentation candidate. if (!lookahead.IsInBounds) { comment = null; break; } lineLookahead.GetBounds(LineBoundsMode.ExcludeWhitespace, out firstToken, out endOfLine); } if (comment != null) { // Mark the symbols before returning firstToken = comment.Start.FirstToken(LineBoundsMode.ExcludeWhitespace); lookahead = firstToken; TryToSkipOpeningBlockCommentSymbol(ref lookahead, out closingSymbol); if (comment.Javadoc) { lookahead.Next(); } firstToken.SetCommentParsingTypeBetween(lookahead, CommentParsingType.CommentSymbol); LineIterator lastLine = comment.End; lastLine.Previous(); lastLine.GetBounds(LineBoundsMode.ExcludeWhitespace, out firstToken, out endOfLine); lookahead = endOfLine; if (hadTrailingDashes) { lookahead.Previous(2); } lookahead.PreviousByCharacters(closingSymbol.Length); lookahead.SetCommentParsingTypeBetween(endOfLine, CommentParsingType.CommentSymbol); } // If we made it this far that means we found a comment and can move the line iterator and return true. Whether // that comment was suitable for documentation will be determined by the comment variable, but we are moving the // iterator and returning true either way. lineIterator = lineLookahead; return(true); }
/* Function: MarkParameter * Marks the tokens in the parameter specified by the bounds with <PrototypeParsingTypes>. */ protected void MarkParameter(TokenIterator start, TokenIterator end) { // Pass 1: Count the number of "words" in the parameter prior to the default value and mark the default value // separator. We'll figure out how to interpret the words in the second pass. int words = 0; TokenIterator iterator = start; while (iterator < end) { // Default values if (iterator.Character == '=') { iterator.PrototypeParsingType = PrototypeParsingType.DefaultValueSeparator; iterator.Next(); iterator.NextPastWhitespace(end); TokenIterator endOfDefaultValue = end; TokenIterator lookbehind = endOfDefaultValue; lookbehind.Previous(); while (lookbehind >= iterator && lookbehind.PrototypeParsingType == PrototypeParsingType.ParamSeparator) { endOfDefaultValue = lookbehind; lookbehind.Previous(); } endOfDefaultValue.PreviousPastWhitespace(PreviousPastWhitespaceMode.EndingBounds, iterator); if (iterator < endOfDefaultValue) { iterator.SetPrototypeParsingTypeBetween(endOfDefaultValue, PrototypeParsingType.DefaultValue); } break; } // Param separator else if (iterator.PrototypeParsingType == PrototypeParsingType.ParamSeparator) { break; } // "Words" we're interested in else if (TryToSkipTypeOrVarName(ref iterator, end) || TryToSkipComment(ref iterator) || TryToSkipString(ref iterator) || TryToSkipBlock(ref iterator, true)) { // If there was a comment in the prototype, that means it specifically wasn't filtered out because it was something // significant like a Splint comment or /*out*/. Treat it like a modifier. // Strings don't really make sense in the prototype until the default value, but we need the parser to handle it anyway // just so it doesn't lose its mind if one occurs. // If we come across a block that doesn't immediately follow an identifier, it may be something like a C# property so // treat it as a modifier. words++; } // Whitespace and any unexpected random symbols else { iterator.Next(); } } // Pass 2: Mark the "words" we counted from the first pass. The order of words goes [modifier] [modifier] [type] [name], // starting from the right. Typeless languages that only have one word will have it correctly interpreted as the name. iterator = start; TokenIterator wordStart, wordEnd; while (iterator < end) { wordStart = iterator; bool foundWord = false; bool foundBlock = false; if (iterator.PrototypeParsingType == PrototypeParsingType.DefaultValueSeparator || iterator.PrototypeParsingType == PrototypeParsingType.ParamSeparator) { break; } else if (TryToSkipTypeOrVarName(ref iterator, end)) { foundWord = true; } else if (TryToSkipComment(ref iterator) || TryToSkipString(ref iterator) || TryToSkipBlock(ref iterator, true)) { foundWord = true; foundBlock = true; } else { iterator.Next(); } // Process the word we found if (foundWord) { wordEnd = iterator; if (words >= 3) { if (foundBlock && wordEnd.TokenIndex - wordStart.TokenIndex >= 2) { wordStart.PrototypeParsingType = PrototypeParsingType.OpeningTypeModifier; TokenIterator lookbehind = wordEnd; lookbehind.Previous(); lookbehind.PrototypeParsingType = PrototypeParsingType.ClosingTypeModifier; } else { wordStart.SetPrototypeParsingTypeBetween(wordEnd, PrototypeParsingType.TypeModifier); } } else if (words == 2) { MarkType(wordStart, wordEnd); } else if (words == 1) { MarkName(wordStart, wordEnd); // Change the $ at the beginning of the name from a param modifier to part of the name if (wordStart.Character == '$') { wordStart.PrototypeParsingType = PrototypeParsingType.Name; } } words--; } } }