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