From 7eedcef77b1b69e3116c90af70e9ad35b3ec4a5b Mon Sep 17 00:00:00 2001 From: Dan Olson Date: Tue, 28 Apr 2015 00:25:16 -0700 Subject: [PATCH] Generate etags Generate Emacs style etags with -e. Uses similar pattern as ctags. This verison scopes all tags with module name and does not generate tags for function or unittest scoped declarations. May want to make some of this configurable and add other stuff like tags for aliases. --- src/etags.d | 235 ++++++++++++++++++++++++++++++++++++++++++++++++++++ src/main.d | 14 +++- 2 files changed, 248 insertions(+), 1 deletion(-) create mode 100644 src/etags.d diff --git a/src/etags.d b/src/etags.d new file mode 100644 index 0000000..ad3abf2 --- /dev/null +++ b/src/etags.d @@ -0,0 +1,235 @@ +// Emacs tags based on ctags.d with this header: +// Copyright Brian Schott (Hackerpilot) 2012. +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) + +module etags; + +import std.d.parser; +import std.d.lexer; +import std.d.ast; +import std.algorithm; +import std.range; +import std.stdio; +import std.path; +import std.array; +import std.conv; + +/** + * Prints ETAGS information to the given file. + * Params: + * outpt = the file that ETAGS info is written to + * fileNames = tags will be generated from these files + */ +void printEtags(File output, string[] fileNames) +{ + LexerConfig config; + StringCache cache = StringCache(StringCache.defaultBucketCount); + foreach (fileName; fileNames) + { + File f = File(fileName); + if (f.size == 0) continue; + auto bytes = uninitializedArray!(ubyte[])(to!size_t(f.size)); + f.rawRead(bytes); + auto tokens = getTokensForParser(bytes, config, &cache); + Module m = parseModule(tokens.array, fileName, null, &doNothing); + auto printer = new EtagsPrinter; + + // I think I like this + enum useModuleContext = true; + if (useModuleContext) + printer.context = m.moduleFullName(fileName) ~ "."; + + printer.bytes = bytes.sansBOM; + printer.visit(m); + output.writef("\f\n%s,%u\n", fileName, printer.tags.length); + printer.tags.copy(output.lockingTextWriter); + } +} + +private: + +void doNothing(string, size_t, size_t, string, bool) {} + +ubyte[] sansBOM(ubyte[] bytes) +{ + // At least handle UTF-8 since there is some in druntime/phobos + return (bytes.length >= 3 && + bytes[0] == 0xef && bytes[1] == 0xbb && bytes[2] == 0xbf) + ? bytes[3 .. $] : bytes; + +} + +string moduleFullName(Module m, string fileName) +{ + // When no module declaration, just use filename and hope its valid + if (!m.moduleDeclaration) + return fileName.baseName.stripExtension; + + // reconstruct module full name + return m.moduleDeclaration.moduleName.identifiers.map!(i=>i.text).join("."); +} + +class EtagsPrinter : ASTVisitor +{ + override void visit(const ClassDeclaration dec) + { + maketag(dec.name); + acceptInContext(dec, dec.name.text); + } + + override void visit(const StructDeclaration dec) + { + maketag(dec.name); + acceptInContext(dec, dec.name.text); + } + + override void visit(const InterfaceDeclaration dec) + { + maketag(dec.name); + acceptInContext(dec, dec.name.text); + } + + override void visit(const TemplateDeclaration dec) + { + maketag(dec.name); + acceptInContext(dec, dec.name.text); + } + + override void visit(const FunctionDeclaration dec) + { + maketag(dec.name); + bool was = inFunc; + inFunc = true; + auto c = context; + acceptInContext(dec, dec.name.text); + inFunc = was; + } + + override void visit(const Constructor dec) + { + maketag("this", dec.location, dec.line); + bool was = inFunc; + inFunc = true; + auto c = context; + acceptInContext(dec, "this"); + inFunc = was; + } + + override void visit(const Destructor dec) + { + maketag("~this", dec.index, dec.line); + bool was = inFunc; + inFunc = true; + auto c = context; + acceptInContext(dec, "~this"); + inFunc = was; + } + + override void visit(const EnumDeclaration dec) + { + maketag(dec.name); + acceptInContext(dec, dec.name.text); + } + + override void visit(const UnionDeclaration dec) + { + if (dec.name == tok!"") + { + dec.accept(this); + return; + } + maketag(dec.name); + acceptInContext(dec, dec.name.text); + } + + override void visit(const AnonymousEnumMember mem) + { + maketag(mem.name); + } + + override void visit(const EnumMember mem) + { + maketag(mem.name); + } + + override void visit(const Unittest dec) + { + bool was = inUnittest; + inUnittest = true; + dec.accept(this); + inUnittest = was; + } + + override void visit(const VariableDeclaration dec) + { + foreach (d; dec.declarators) + { + maketag(d.name); + } + dec.accept(this); + } + + override void visit(const AutoDeclaration dec) + { + foreach (i; dec.identifiers) + { + maketag(i); + } + dec.accept(this); + } + + override void visit(const Invariant dec) + { + maketag("invariant", dec.index, dec.line); + } + + void acceptInContext(Dec)(Dec dec, string name) + { + // nest context before journeying on + auto c = context; + context ~= name ~ "."; + dec.accept(this); + context = c; + } + + void maketag(Token name) + { + maketag(name.text, name.index, name.line); + } + + void maketag(string text, ulong index, ulong line) + { + // skip declaration in unittests and funcs + if (inUnittest || inFunc) return; + + // tag is a searchable string from beginning of line + ulong b = index; + while (b > 0 && bytes[--b] != '\n') {} + ++b; + + // tag end is one char beyond tag name + ulong e = index + text.length; + if (e < bytes.length && bytes[e] != '\n') ++e; + + auto tag = cast(char[])bytes[b..e]; + auto tagname = context.empty ? text : context~text; + + // drum roll... the etags tag format + tags ~= format("%s\x7f%s\x01%u,%u\n", + tag, + tagname, + line, + b); + } + + alias visit = ASTVisitor.visit; + + bool inUnittest; + bool inFunc; + ubyte[] bytes; + string tags; + string context; +} + diff --git a/src/main.d b/src/main.d index 9047b36..d603c0a 100644 --- a/src/main.d +++ b/src/main.d @@ -20,6 +20,7 @@ import std.d.parser; import highlighter; import stats; import ctags; +import etags; import astprinter; import imports; import outliner; @@ -47,6 +48,7 @@ int run(string[] args) bool sloc; bool highlight; bool ctags; + bool etags; bool help; bool tokenCount; bool syntaxCheck; @@ -67,6 +69,7 @@ int run(string[] args) { getopt(args, std.getopt.config.caseSensitive, "sloc|l", &sloc, "highlight", &highlight, "ctags|c", &ctags, "help|h", &help, + "etags|e", &etags, "tokenCount|t", &tokenCount, "syntaxCheck|s", &syntaxCheck, "ast|xml", &ast, "imports|i", &imports, "outline|o", &outline, "tokenDump", &tokenDump, "styleCheck|S", &styleCheck, @@ -117,7 +120,7 @@ int run(string[] args) return 0; } - auto optionCount = count!"a"([sloc, highlight, ctags, tokenCount, + auto optionCount = count!"a"([sloc, highlight, ctags, etags, tokenCount, syntaxCheck, ast, imports, outline, tokenDump, styleCheck, defaultConfig, report, symbolName !is null]); if (optionCount > 1) @@ -174,6 +177,10 @@ int run(string[] args) { stdout.printCtags(expandArgs(args)); } + else if (etags) + { + stdout.printEtags(expandArgs(args)); + } else if (styleCheck) { StaticAnalysisConfig config = defaultStaticAnalysisConfig(); @@ -357,6 +364,11 @@ options: ctags information requires a filename, so stdin cannot be used in place of a filename. + --etags | -e sourceFile + Generates etags information from the given source code file. Note that + etags information requires a filename, so stdin cannot be used in place + of a filename. + --ast | --xml sourceFile Generates an XML representation of the source files abstract syntax tree. If no files are specified, input is read from stdin.