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