1 // Emacs tags based on ctags.d with this header: 2 // Copyright Brian Schott (Hackerpilot) 2012. 3 // Distributed under the Boost Software License, Version 1.0. 4 // (See accompanying file LICENSE_1_0.txt or copy at 5 // http://www.boost.org/LICENSE_1_0.txt) 6 7 module etags; 8 9 import dparse.parser; 10 import dparse.lexer; 11 import dparse.ast; 12 import std.algorithm; 13 import std.range; 14 import std.stdio; 15 import std.path; 16 import std.array; 17 import std.conv; 18 19 // Prefix tags with their module name. Seems like correct behavior, but just 20 // in case, make it an option. 21 version = UseModuleContext; 22 23 // Could not find "official" definition of protection (public/private/etc). 24 // Behavior modeled here was reversed engineered based on dmd 2.067. 25 // class/interface and non-anonymous struct/union/enum members default to 26 // public, regardless of the enclosing declaration. template and anonymous 27 // struct/union/enum members default to the enclosing protection. 28 29 /** 30 * Prints ETAGS information to the given file. 31 * Params: 32 * outpt = the file that ETAGS info is written to 33 * tagAll = if set, tag private/package declaration too 34 * fileNames = tags will be generated from these files 35 */ 36 void printEtags(File output, bool tagAll, string[] fileNames) 37 { 38 LexerConfig config; 39 StringCache cache = StringCache(StringCache.defaultBucketCount); 40 foreach (fileName; fileNames) 41 { 42 File f = File(fileName); 43 if (f.size == 0) continue; 44 auto bytes = uninitializedArray!(ubyte[])(to!size_t(f.size)); 45 f.rawRead(bytes); 46 auto tokens = getTokensForParser(bytes, config, &cache); 47 Module m = parseModule(tokens.array, fileName, null, &doNothing); 48 49 auto printer = new EtagsPrinter; 50 printer.moduleName = m.moduleFullName(fileName); 51 version(UseModuleContext) 52 printer.context = printer.moduleName ~ "."; 53 printer.privateVisibility = tagAll? 54 Visibility.exposed : 55 Visibility.hidden; 56 printer.bytes = bytes.sansBOM; 57 printer.visit(m); 58 59 output.writef("\f\n%s,%u\n", fileName, printer.tags.length); 60 printer.tags.copy(output.lockingTextWriter); 61 } 62 } 63 64 private: 65 66 enum Visibility {exposed, hidden} 67 68 void doNothing(string, size_t, size_t, string, bool) {} 69 70 ubyte[] sansBOM(ubyte[] bytes) 71 { 72 // At least handle UTF-8 since there is some in druntime/phobos 73 return (bytes.length >= 3 && 74 bytes[0] == 0xef && bytes[1] == 0xbb && bytes[2] == 0xbf) 75 ? bytes[3 .. $] : bytes; 76 } 77 78 string moduleFullName(Module m, string fileName) 79 { 80 // When no module declaration, just use filename and hope its valid 81 if (!m.moduleDeclaration) 82 return fileName.baseName.stripExtension; 83 84 // reconstruct module full name 85 return m.moduleDeclaration.moduleName.identifiers.map!(i=>i.text).join("."); 86 } 87 88 final class EtagsPrinter : ASTVisitor 89 { 90 override void visit(const ModuleDeclaration dec) 91 { 92 auto tok0 = dec.moduleName.identifiers[0]; 93 auto was = context; 94 context = ""; 95 maketag(moduleName, tok0.index, tok0.line); 96 context = was; 97 dec.accept(this); 98 } 99 100 override void visit(const Declaration dec) 101 { 102 // Update value of visibility based on this 'dec'. 103 if (dec.attributeDeclaration) 104 { 105 auto attr = dec.attributeDeclaration.attribute; 106 updateVisibility(attr.attribute.type); 107 } 108 109 // visibility needs to be restored to what it was when changed by 110 // attribute. 111 auto was = visibility; 112 foreach (attr; dec.attributes) { 113 updateVisibility(attr.attribute.type); 114 } 115 116 dec.accept(this); 117 visibility = was; 118 } 119 120 override void visit(const ClassDeclaration dec) 121 { 122 maketag(dec.name); 123 // class members default to public 124 visibility = Visibility.exposed; 125 acceptInContext(dec, dec.name.text); 126 } 127 128 override void visit(const StructDeclaration dec) 129 { 130 if (dec.name == tok!"") 131 { 132 dec.accept(this); 133 return; 134 } 135 maketag(dec.name); 136 // struct members default to public 137 visibility = Visibility.exposed; 138 acceptInContext(dec, dec.name.text); 139 } 140 141 override void visit(const InterfaceDeclaration dec) 142 { 143 maketag(dec.name); 144 // interface members default to public 145 visibility = Visibility.exposed; 146 acceptInContext(dec, dec.name.text); 147 } 148 149 override void visit(const TemplateDeclaration dec) 150 { 151 maketag(dec.name); 152 acceptInContext(dec, dec.name.text); 153 } 154 155 override void visit(const FunctionDeclaration dec) 156 { 157 maketag(dec.name); 158 // don't tag declarations in a function like thing 159 visibility = Visibility.hidden; 160 acceptInContext(dec, dec.name.text); 161 } 162 163 override void visit(const Constructor dec) 164 { 165 maketag("this", dec.location, dec.line); 166 // don't tag declarations in a function like thing 167 visibility = Visibility.hidden; 168 acceptInContext(dec, "this"); 169 } 170 171 override void visit(const Destructor dec) 172 { 173 maketag("~this", dec.index, dec.line); 174 // don't tag declarations in a function like thing 175 visibility = Visibility.hidden; 176 acceptInContext(dec, "~this"); 177 } 178 179 override void visit(const EnumDeclaration dec) 180 { 181 maketag(dec.name); 182 // enum members default to public 183 visibility = Visibility.exposed; 184 acceptInContext(dec, dec.name.text); 185 } 186 187 override void visit(const UnionDeclaration dec) 188 { 189 if (dec.name == tok!"") 190 { 191 dec.accept(this); 192 return; 193 } 194 maketag(dec.name); 195 // union members default to public 196 visibility = Visibility.exposed; 197 acceptInContext(dec, dec.name.text); 198 } 199 200 override void visit(const AnonymousEnumMember mem) 201 { 202 maketag(mem.name); 203 } 204 205 override void visit(const EnumMember mem) 206 { 207 maketag(mem.name); 208 } 209 210 override void visit(const Unittest dec) 211 { 212 bool was = inUnittest; 213 inUnittest = true; 214 dec.accept(this); 215 inUnittest = was; 216 } 217 218 override void visit(const VariableDeclaration dec) 219 { 220 foreach (d; dec.declarators) 221 { 222 maketag(d.name); 223 } 224 dec.accept(this); 225 } 226 227 override void visit(const AutoDeclaration dec) 228 { 229 foreach (i; dec.identifiers) 230 { 231 maketag(i); 232 } 233 dec.accept(this); 234 } 235 236 override void visit(const AliasDeclaration dec) 237 { 238 // Old style alias 239 if (dec.identifierList) 240 { 241 foreach (i; dec.identifierList.identifiers) 242 maketag(i); 243 } 244 dec.accept(this); 245 } 246 247 override void visit(const AliasInitializer dec) 248 { 249 maketag(dec.name); 250 dec.accept(this); 251 } 252 253 override void visit(const Invariant dec) 254 { 255 maketag("invariant", dec.index, dec.line); 256 } 257 258 private: 259 void updateVisibility(IdType type) { 260 // maybe change visibility based on attribute 'type' 261 switch (type) 262 { 263 case tok!"export": 264 case tok!"public": 265 case tok!"protected": 266 visibility = Visibility.exposed; 267 break; 268 case tok!"package": 269 case tok!"private": 270 visibility = privateVisibility; 271 break; 272 default: 273 // no change 274 break; 275 } 276 } 277 278 void acceptInContext(const ASTNode dec, string name) 279 { 280 // nest context before journeying on 281 auto c = context; 282 context ~= name ~ "."; 283 dec.accept(this); 284 context = c; 285 } 286 287 void maketag(Token name) 288 { 289 maketag(name.text, name.index, name.line); 290 } 291 292 void maketag(string text, size_t index, ulong line) 293 { 294 // skip unittests and hidden declarations 295 if (inUnittest || visibility == Visibility.hidden) return; 296 297 // tag is a searchable string from beginning of line 298 size_t b = index; 299 while (b > 0 && bytes[b-1] != '\n') --b; 300 301 // tag end is one char beyond tag name 302 size_t e = index + text.length; 303 if (e < bytes.length && bytes[e] != '\n') ++e; 304 305 auto tag = cast(char[])bytes[b..e]; 306 auto tagname = context.empty ? text : context~text; 307 308 // drum roll... the etags tag format 309 tags ~= format("%s\x7f%s\x01%u,%u\n", 310 tag, 311 tagname, 312 line, 313 b); 314 } 315 316 alias visit = ASTVisitor.visit; 317 318 // state 319 // visibility of declarations (i.e. should we tag) 320 Visibility visibility = Visibility.exposed; 321 bool inUnittest; 322 323 // inputs 324 ubyte[] bytes; 325 string moduleName; 326 string context; 327 Visibility privateVisibility = Visibility.hidden; 328 329 // ouput 330 string tags; 331 }