1 /** 2 * Copyright: Copyright (c) 2016 Wojciech Szęszoł. All rights reserved. 3 * Authors: Wojciech Szęszoł 4 * Version: Initial created: May 19, 2016 5 * License: $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost Software License 1.0) 6 */ 7 module dstep.translator.CommentIndex; 8 9 import std.range; 10 11 import clang.c.Index; 12 import clang.Token; 13 import clang.SourceLocation; 14 import clang.SourceRange; 15 import clang.TranslationUnit; 16 17 class CommentIndex 18 { 19 public struct Comment 20 { 21 string content; 22 SourceRange extent; 23 uint line; 24 uint column; 25 uint offset; 26 27 this(Token token) 28 { 29 auto location = token.location; 30 content = normalize(token.spelling); 31 extent = token.extent; 32 line = location.line; 33 column = location.column; 34 offset = location.offset; 35 } 36 37 size_t indentAmount() const 38 { 39 import std..string; 40 import std.format; 41 import std.algorithm.comparison; 42 import std.algorithm.searching; 43 44 size_t amount = this.column - 1; 45 auto lines = lineSplitter(content); 46 47 if (!lines.empty) 48 { 49 lines.popFront(); 50 51 foreach (line; lines) 52 { 53 amount = min( 54 amount, 55 cast(size_t) countUntil!(a => a != ' ')(line)); 56 } 57 } 58 59 return amount; 60 } 61 62 int opCmp(ref const Comment s) const 63 { 64 return offset < s.offset ? -1 : (offset == s.offset ? 0 : 1); 65 } 66 67 int opCmp(uint s) const 68 { 69 return offset < s ? -1 : (offset == s ? 0 : 1); 70 } 71 72 private static bool isNormalized(string content) 73 { 74 import std.ascii : isWhite; 75 import std.range : iota; 76 import std.algorithm : canFind; 77 78 return !iota(0, content.length).canFind!( 79 i => content[i] == '\n' && content[i - 1].isWhite); 80 } 81 82 private static string normalize(string content) 83 { 84 import std.algorithm : map, splitter; 85 import std..string : stripRight; 86 87 if (isNormalized(content)) 88 return content; 89 else 90 return content.splitter("\n").map!(stripRight).join("\n"); 91 } 92 93 unittest 94 { 95 assert(normalize("") == ""); 96 assert(normalize("foo") == "foo"); 97 assert(normalize("foo \n") == "foo\n"); 98 assert(normalize("foo \n ") == "foo\n"); 99 assert(normalize("foo \n bar \n ") == "foo\n bar\n"); 100 assert(normalize("foo \n bar \n\n ") == "foo\n bar\n\n"); 101 } 102 } 103 104 private TranslationUnit translUnit; 105 private Comment[] comments; 106 private SourceRange lastTokenRange; 107 private SourceLocation includeGuardLocation; 108 private bool hasIncludeGuard = false; 109 110 this(TranslationUnit translUnit) 111 { 112 import std.algorithm.iteration : filter, map; 113 114 this.translUnit = translUnit; 115 lastTokenRange = translUnit.cursor.extent; 116 117 auto tokens = translUnit.cursor.tokens; 118 119 if (!tokens.empty) 120 lastTokenRange = tokens.back.extent; 121 122 comments = tokens 123 .filter!(token => 124 token.kind == TokenKind.comment && 125 token.location.isFromMainFile) 126 .map!(token => Comment(token)).array; 127 } 128 129 this (TranslationUnit translUnit, SourceLocation includeGuardLocation) 130 { 131 this (translUnit); 132 133 this.includeGuardLocation = includeGuardLocation; 134 this.hasIncludeGuard = true; 135 } 136 137 auto queryComments(uint begin, uint end) 138 { 139 auto sorted = assumeSorted(comments); 140 auto lower = sorted.lowerBound(end); 141 142 if (lower.empty || begin == 0) 143 return lower; 144 else 145 return lower.upperBound(begin - 1); 146 } 147 148 SourceLocation queryLastLocation() 149 { 150 return lastTokenRange.end; 151 } 152 153 /** 154 * Returns true if a header comment is present. 155 * 156 * If include guard is present the header comment consists of all of the 157 * comments before the header guard. Otherwise, the header comment is a 158 * comment placed at the very beginning of the file, specifically it cannot 159 * have any white-spaces before it. 160 */ 161 bool isHeaderCommentPresent() 162 { 163 if (hasIncludeGuard) 164 return !comments.empty && 165 comments.front.extent.end.offset < includeGuardLocation.offset; 166 else 167 return !comments.empty && comments.front.offset == 0; 168 } 169 170 SourceRange queryHeaderCommentExtent() 171 { 172 if (hasIncludeGuard) 173 { 174 import std.algorithm.searching : find; 175 176 auto offset = includeGuardLocation.offset; 177 auto prv = comments.front; 178 179 foreach (itr; comments) 180 { 181 if (itr.offset >= offset) 182 break; 183 184 prv = itr; 185 } 186 187 return translUnit.extent( 188 comments.front.offset, 189 prv.extent.end.offset); 190 } 191 else 192 { 193 return translUnit.extent( 194 comments.front.offset, 195 comments.front.offset + cast(uint) comments.front.content.length); 196 } 197 } 198 }