1 // Copyright Brian Schott (Hackerpilot) 2012. 2 // Distributed under the Boost Software License, Version 1.0. 3 // (See accompanying file LICENSE_1_0.txt or copy at 4 // http://www.boost.org/LICENSE_1_0.txt) 5 6 module ctags; 7 8 import dparse.parser; 9 import dparse.lexer; 10 import dparse.ast; 11 import dparse.rollback_allocator; 12 import std.algorithm; 13 import std.range; 14 import std.stdio; 15 import std.array; 16 import std.conv; 17 import std.typecons; 18 import containers.ttree; 19 import std.string; 20 21 /** 22 * Prints CTAGS information to the given file. 23 * Includes metadata according to exuberant format used by Vim. 24 * Params: 25 * output = the file that Exuberant TAGS info is written to 26 * fileNames = tags will be generated from these files 27 */ 28 void printCtags(File output, string[] fileNames) 29 { 30 TTree!string tags; 31 LexerConfig config; 32 StringCache cache = StringCache(StringCache.defaultBucketCount); 33 foreach (fileName; fileNames) 34 { 35 RollbackAllocator rba; 36 File f = File(fileName); 37 if (f.size == 0) 38 continue; 39 auto bytes = uninitializedArray!(ubyte[])(to!size_t(f.size)); 40 f.rawRead(bytes); 41 auto tokens = getTokensForParser(bytes, config, &cache); 42 Module m = parseModule(tokens.array, fileName, &rba, &doNothing); 43 44 auto printer = new CTagsPrinter(&tags); 45 printer.fileName = fileName; 46 printer.visit(m); 47 } 48 output.write("!_TAG_FILE_FORMAT\t2\n" ~ "!_TAG_FILE_SORTED\t1\n" ~ "!_TAG_FILE_AUTHOR\tBrian Schott\n" 49 ~ "!_TAG_PROGRAM_URL\thttps://github.com/Hackerpilot/Dscanner/\n"); 50 tags[].copy(output.lockingTextWriter); 51 } 52 53 private: 54 55 /// States determining how an access modifier affects tags when traversing the 56 /// AST. 57 /// The assumption is that there are fewer AST nodes and patterns that affects 58 /// the whole scope. 59 /// Therefor the default was chosen to be Reset. 60 enum AccessState 61 { 62 Reset, /// when ascending the AST reset back to the previous access. 63 Keep /// when ascending the AST keep the new access. 64 } 65 66 alias ContextType = Tuple!(string, "c", string, "access"); 67 68 void doNothing(string, size_t, size_t, string, bool) 69 { 70 } 71 72 string paramsToString(Dec)(const Dec dec) 73 { 74 import dparse.formatter : Formatter; 75 76 auto app = appender!string(); 77 auto formatter = new Formatter!(typeof(app))(app); 78 79 static if (is(Dec == FunctionDeclaration) || is(Dec == Constructor)) 80 { 81 formatter.format(dec.parameters); 82 } 83 else static if (is(Dec == TemplateDeclaration)) 84 { 85 formatter.format(dec.templateParameters); 86 } 87 88 return app.data; 89 } 90 91 final class CTagsPrinter : ASTVisitor 92 { 93 public this(TTree!string* output) 94 { 95 this.tagLines = output; 96 } 97 98 override void visit(const ClassDeclaration dec) 99 { 100 tagLines.insert("%s\t%s\t%d;\"\tc\tline:%d%s%s\n".format(dec.name.text, 101 fileName, dec.name.line, dec.name.line, context.c, context.access)); 102 immutable c = context; 103 context = ContextType("\tclass:" ~ dec.name.text, "\taccess:public"); 104 dec.accept(this); 105 context = c; 106 } 107 108 override void visit(const StructDeclaration dec) 109 { 110 if (dec.name == tok!"") 111 { 112 dec.accept(this); 113 return; 114 } 115 tagLines.insert("%s\t%s\t%d;\"\ts\tline:%d%s%s\n".format(dec.name.text, 116 fileName, dec.name.line, dec.name.line, context.c, context.access)); 117 immutable c = context; 118 context = ContextType("\tstruct:" ~ dec.name.text, "\taccess:public"); 119 dec.accept(this); 120 context = c; 121 } 122 123 override void visit(const InterfaceDeclaration dec) 124 { 125 tagLines.insert("%s\t%s\t%d;\"\ti\tline:%d%s%s\n".format(dec.name.text, 126 fileName, dec.name.line, dec.name.line, context.c, context.access)); 127 immutable c = context; 128 context = ContextType("\tclass:" ~ dec.name.text, context.access); 129 dec.accept(this); 130 context = c; 131 } 132 133 override void visit(const TemplateDeclaration dec) 134 { 135 auto params = paramsToString(dec); 136 137 tagLines.insert("%s\t%s\t%d;\"\tT\tline:%d%s%s\tsignature:%s\n".format(dec.name.text, 138 fileName, dec.name.line, dec.name.line, context.c, context.access, params)); 139 immutable c = context; 140 context = ContextType("\ttemplate:" ~ dec.name.text, context.access); 141 dec.accept(this); 142 context = c; 143 } 144 145 override void visit(const FunctionDeclaration dec) 146 { 147 auto params = paramsToString(dec); 148 149 tagLines.insert("%s\t%s\t%d;\"\tf\tline:%d%s%s\tsignature:%s\n".format(dec.name.text, 150 fileName, dec.name.line, dec.name.line, context.c, context.access, params)); 151 } 152 153 override void visit(const Constructor dec) 154 { 155 auto params = paramsToString(dec); 156 157 tagLines.insert("this\t%s\t%d;\"\tf\tline:%d%s\tsignature:%s%s\n".format(fileName, 158 dec.line, dec.line, context.c, params, context.access)); 159 } 160 161 override void visit(const Destructor dec) 162 { 163 tagLines.insert("~this\t%s\t%d;\"\tf\tline:%d%s\n".format(fileName, 164 dec.line, dec.line, context.c)); 165 } 166 167 override void visit(const EnumDeclaration dec) 168 { 169 tagLines.insert("%s\t%s\t%d;\"\tg\tline:%d%s%s\n".format(dec.name.text, 170 fileName, dec.name.line, dec.name.line, context.c, context.access)); 171 immutable c = context; 172 context = ContextType("\tenum:" ~ dec.name.text, context.access); 173 dec.accept(this); 174 context = c; 175 } 176 177 override void visit(const UnionDeclaration dec) 178 { 179 if (dec.name == tok!"") 180 { 181 dec.accept(this); 182 return; 183 } 184 tagLines.insert("%s\t%s\t%d;\"\tu\tline:%d%s\n".format(dec.name.text, 185 fileName, dec.name.line, dec.name.line, context.c)); 186 immutable c = context; 187 context = ContextType("\tunion:" ~ dec.name.text, context.access); 188 dec.accept(this); 189 context = c; 190 } 191 192 override void visit(const AnonymousEnumMember mem) 193 { 194 tagLines.insert("%s\t%s\t%d;\"\te\tline:%d%s\n".format(mem.name.text, 195 fileName, mem.name.line, mem.name.line, context.c)); 196 } 197 198 override void visit(const EnumMember mem) 199 { 200 tagLines.insert("%s\t%s\t%d;\"\te\tline:%d%s\n".format(mem.name.text, 201 fileName, mem.name.line, mem.name.line, context.c)); 202 } 203 204 override void visit(const VariableDeclaration dec) 205 { 206 foreach (d; dec.declarators) 207 { 208 tagLines.insert("%s\t%s\t%d;\"\tv\tline:%d%s%s\n".format(d.name.text, 209 fileName, d.name.line, d.name.line, context.c, context.access)); 210 } 211 dec.accept(this); 212 } 213 214 override void visit(const AutoDeclaration dec) 215 { 216 foreach (i; dec.identifiers) 217 { 218 tagLines.insert("%s\t%s\t%d;\"\tv\tline:%d%s%s\n".format(i.text, 219 fileName, i.line, i.line, context.c, context.access)); 220 } 221 dec.accept(this); 222 } 223 224 override void visit(const Invariant dec) 225 { 226 tagLines.insert("invariant\t%s\t%d;\"\tv\tline:%d%s%s\n".format(fileName, 227 dec.line, dec.line, context.c, context.access)); 228 } 229 230 override void visit(const ModuleDeclaration dec) 231 { 232 context = ContextType("", "\taccess:public"); 233 dec.accept(this); 234 } 235 236 override void visit(const Attribute attribute) 237 { 238 if (attribute.attribute != tok!"") 239 { 240 switch (attribute.attribute.type) 241 { 242 case tok!"export": 243 context.access = "\taccess:public"; 244 break; 245 case tok!"public": 246 context.access = "\taccess:public"; 247 break; 248 case tok!"package": 249 context.access = "\taccess:protected"; 250 break; 251 case tok!"protected": 252 context.access = "\taccess:protected"; 253 break; 254 case tok!"private": 255 context.access = "\taccess:private"; 256 break; 257 default: 258 } 259 } 260 261 attribute.accept(this); 262 } 263 264 override void visit(const AttributeDeclaration dec) 265 { 266 accessSt = AccessState.Keep; 267 dec.accept(this); 268 } 269 270 override void visit(const Declaration dec) 271 { 272 immutable c = context; 273 dec.accept(this); 274 275 final switch (accessSt) with (AccessState) 276 { 277 case Reset: 278 context = c; 279 break; 280 case Keep: 281 break; 282 } 283 accessSt = AccessState.Reset; 284 } 285 286 override void visit(const Unittest dec) 287 { 288 // skipping symbols inside a unit test to not clutter the ctags file 289 // with "temporary" symbols. 290 // TODO when phobos have a unittest library investigate how that could 291 // be used to describe the tests. 292 // Maybe with UDA's to give the unittest a "name". 293 } 294 295 override void visit(const AliasDeclaration dec) 296 { 297 // Old style alias 298 if (dec.identifierList) 299 { 300 foreach (i; dec.identifierList.identifiers) 301 { 302 tagLines.insert("%s\t%s\t%d;\"\ta\tline:%d%s%s\n".format(i.text, 303 fileName, i.line, i.line, context.c, context.access)); 304 } 305 } 306 dec.accept(this); 307 } 308 309 override void visit(const AliasInitializer dec) 310 { 311 tagLines.insert("%s\t%s\t%d;\"\ta\tline:%d%s%s\n".format(dec.name.text, 312 fileName, dec.name.line, dec.name.line, context.c, context.access)); 313 314 dec.accept(this); 315 } 316 317 override void visit(const AliasThisDeclaration dec) 318 { 319 auto name = dec.identifier; 320 tagLines.insert("%s\t%s\t%d;\"\ta\tline:%d%s%s\n".format(name.text, 321 fileName, name.line, name.line, context.c, context.access)); 322 323 dec.accept(this); 324 } 325 326 alias visit = ASTVisitor.visit; 327 string fileName; 328 TTree!string* tagLines; 329 ContextType context; 330 AccessState accessSt; 331 }