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