/// <summary> /// Process comments up until nextElement. /// Group comments into blocks, and associate blocks with elements. /// </summary> /// /// <param name="commentEnumerator">Enumerator for all comments in the program.</param> /// <param name="nextElement">The next element in the list.</param> /// <param name="elementStack">A stack of nested program elements.</param> /// <param name="cb">Where to send the results.</param> /// <returns>true if there are more comments to process, false otherwise.</returns> bool GenerateBindings( IEnumerator <KeyValuePair <Location, ICommentLine> > commentEnumerator, KeyValuePair <Location, Label>?nextElement, ElementStack elementStack, CommentBindingCallback cb ) { CommentBlock block = new CommentBlock(); // Iterate comments until the commentEnumerator has gone past nextElement while (nextElement == null || Compare(commentEnumerator.Current.Value.Location, nextElement.Value.Key) < 0) { if (!block.CombinesWith(commentEnumerator.Current.Value)) { // Start of a new block, so generate the bindings for the old block first. GenerateBindings(block, elementStack, nextElement, cb); block = new CommentBlock(); } block.AddCommentLine(commentEnumerator.Current.Value); // Get the next comment. if (!commentEnumerator.MoveNext()) { // If there are no more comments, generate the remaining bindings and return false. GenerateBindings(block, elementStack, nextElement, cb); return(false); } } GenerateBindings(block, elementStack, nextElement, cb); return(true); }
// Generate binding information for one CommentBlock. private void GenerateBindings( ICommentBlock block, ElementStack elementStack, KeyValuePair <Location, Label>?nextElement, CommentBindingCallback cb ) { if (block.CommentLines.Any()) { GenerateBindings( block, elementStack.FindBefore(block.Location), elementStack.FindAfter(block.Location, nextElement), elementStack.FindParent(block.Location), cb); } }
/// <summary> /// Merge comments into blocks and associate comment blocks with program elements. /// </summary> /// <param name="cb">Callback for the binding information</param> public void GenerateBindings(CommentBindingCallback cb) { /* Algorithm: * Do a merge of elements and comments, which are both sorted in location order. * * Iterate through all elements, and iterate all comment lines between adjacent pairs of elements. * Maintain a stack of elements, such that the top of the stack must be fully nested in the * element below it. This enables comments to be associated with the "parent" element, as well as * elements before, after and "best" element match for a comment. * * This is an O(n) algorithm because the list of elements and comments are traversed once. * (Note that comment processing is O(n.log n) overall due to dictionary of elements and comments.) */ ElementStack elementStack = new ElementStack(); using (IEnumerator <KeyValuePair <Location, Label> > elementEnumerator = elements.GetEnumerator()) using (IEnumerator <KeyValuePair <Location, ICommentLine> > commentEnumerator = comments.GetEnumerator()) { if (!commentEnumerator.MoveNext()) { // There are no comments to process. return; } while (elementEnumerator.MoveNext()) { if (!GenerateBindings(commentEnumerator, elementEnumerator.Current, elementStack, cb)) { // No more comments to process. return; } elementStack.Push(elementEnumerator.Current); } // Generate remaining comments at end of file GenerateBindings(commentEnumerator, null, elementStack, cb); } }
/// <summary> /// Generate the bindings between a comment and program elements. /// Called once for each commentBlock. /// </summary> /// /// <param name="commentBlock">The comment block.</param> /// <param name="previousElement">The element before the comment block.</param> /// <param name="nextElement">The element after the comment block.</param> /// <param name="parentElement">The parent element of the comment block.</param> /// <param name="callback">Output binding information.</param> private void GenerateBindings( Comments.CommentBlock commentBlock, KeyValuePair <Location, Label>?previousElement, KeyValuePair <Location, Label>?nextElement, KeyValuePair <Location, Label>?parentElement, CommentBindingCallback callback ) { EnsureSameFile(commentBlock, ref previousElement); EnsureSameFile(commentBlock, ref nextElement); EnsureSameFile(commentBlock, ref parentElement); if (previousElement is not null) { var key = previousElement.Value.Value; callback(key, GetDuplicationGuardKey(key), commentBlock, CommentBinding.Before); } if (nextElement is not null) { var key = nextElement.Value.Value; callback(key, GetDuplicationGuardKey(key), commentBlock, CommentBinding.After); } if (parentElement is not null) { var key = parentElement.Value.Value; callback(key, GetDuplicationGuardKey(key), commentBlock, CommentBinding.Parent); } // Heuristic to decide which is the "best" element associated with the comment. KeyValuePair <Location, Label>?bestElement; if (previousElement is not null && previousElement.Value.Key.EndLine() == commentBlock.Location.StartLine()) { // 1. If the comment is on the same line as the previous element, use that bestElement = previousElement; }
/// <summary> /// Generate the bindings between a comment and program elements. /// Called once for each commentBlock. /// </summary> /// /// <param name="commentBlock">The comment block.</param> /// <param name="previousElement">The element before the comment block.</param> /// <param name="nextElement">The element after the comment block.</param> /// <param name="parentElement">The parent element of the comment block.</param> /// <param name="callback">Output binding information.</param> private void GenerateBindings( ICommentBlock commentBlock, KeyValuePair <Location, Label>?previousElement, KeyValuePair <Location, Label>?nextElement, KeyValuePair <Location, Label>?parentElement, CommentBindingCallback callback ) { EnsureSameFile(commentBlock, ref previousElement); EnsureSameFile(commentBlock, ref nextElement); EnsureSameFile(commentBlock, ref parentElement); if (previousElement != null) { var key = previousElement.Value.Value; callback(key, GetDuplicationGuardKey(key), commentBlock, CommentBinding.Before); } if (nextElement != null) { var key = nextElement.Value.Value; callback(key, GetDuplicationGuardKey(key), commentBlock, CommentBinding.After); } if (parentElement != null) { var key = parentElement.Value.Value; callback(key, GetDuplicationGuardKey(key), commentBlock, CommentBinding.Parent); } // Heuristic to decide which is the "best" element associated with the comment. KeyValuePair <Location, Label>?bestElement; if (previousElement != null && previousElement.Value.Key.EndLine() == commentBlock.Location.StartLine()) { // 1. If the comment is on the same line as the previous element, use that bestElement = previousElement; } else if (nextElement != null && nextElement.Value.Key.StartLine() == commentBlock.Location.EndLine()) { // 2. If the comment is on the same line as the next element, use that bestElement = nextElement; } else if (nextElement != null && previousElement != null && previousElement.Value.Key.EndLine() + 1 == commentBlock.Location.StartLine() && commentBlock.Location.EndLine() + 1 == nextElement.Value.Key.StartLine()) { // 3. If comment is equally between two elements, use the parentElement // because it's ambiguous whether the comment refers to the next or previous element bestElement = parentElement; } else if (nextElement != null && nextElement.Value.Key.StartLine() == commentBlock.Location.EndLine() + 1) { // 4. If there is no gap after the comment, use "nextElement" bestElement = nextElement; } else if (previousElement != null && previousElement.Value.Key.EndLine() + 1 == commentBlock.Location.StartLine()) { // 5. If there is no gap before the comment, use previousElement bestElement = previousElement; } else { // 6. Otherwise, bind the comment to the parent block. bestElement = parentElement; /* if parentElement==null, then there is no best element. The comment is effectively orphaned. * * This can be caused by comments that are not in a type declaration. * Due to restrictions in the dbscheme, the comment cannot be associated with the "file" * which is not an element, and the "using" declarations are not emitted by the extractor. */ } if (bestElement != null) { var label = bestElement.Value.Value; callback(label, GetDuplicationGuardKey(label), commentBlock, CommentBinding.Best); } }