1 /** 2 Date: 2015-2017, Joakim Brännström 3 License: $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost Software License 1.0) 4 Author: Joakim Brännström (joakim.brannstrom@gmx.com) 5 */ 6 module devtool.tok_main; 7 8 import std.conv; 9 import std.stdio; 10 import std..string; 11 import std.typecons : Yes; 12 import logger = std.experimental.logger; 13 14 import clang.c.Index; 15 16 import clang.Util; 17 18 void showClangVersion() { 19 CXString version_ = clang_getClangVersion(); 20 writef("%s\n", toD(version_)); 21 } 22 23 void showAllTokens(ref CXTranslationUnit tu, CXToken* tokens, uint numTokens) { 24 static auto _getTokenKindSpelling(CXTokenKind kind) { 25 with (CXTokenKind) switch (kind) { 26 case punctuation: 27 return "Punctuation"; 28 case keyword: 29 return "Keyword"; 30 case identifier: 31 return "Identifier"; 32 case literal: 33 return "Literal"; 34 case comment: 35 return "Comment"; 36 default: 37 return "Unknown"; 38 } 39 } 40 41 writeln("=== show tokens ==="); 42 writef("NumTokens: %d\n", numTokens); 43 for (auto i = 0U; i < numTokens; i++) { 44 CXToken* token = &tokens[i]; 45 CXTokenKind kind = clang_getTokenKind(*token); 46 CXString spell = clang_getTokenSpelling(tu, *token); 47 CXSourceLocation loc = clang_getTokenLocation(tu, *token); 48 49 CXFile file; 50 uint line, column, offset; 51 clang_getFileLocation(loc, &file, &line, &column, &offset); 52 CXString fileName = clang_getFileName(file); 53 54 writef("Token: %d\n", i); 55 writef(" Text: %s\n", toD(spell)); 56 writef(" Kind: %s\n", _getTokenKindSpelling(kind)); 57 writef(" Location: %s:%d:%d:%d\n", toD(fileName), line, column, offset); 58 writef("\n"); 59 60 clang_disposeString(fileName); 61 } 62 } 63 64 CXSourceRange get_filerange(ref CXTranslationUnit tu, in string filename) { 65 auto getFileSize(in string fileName) { 66 import std.file; 67 68 return getSize(fileName); 69 } 70 71 CXFile file = clang_getFile(tu, filename.toStringz); 72 uint fileSize = cast(uint) getFileSize(filename); 73 74 // get top/last location of the file 75 CXSourceLocation topLoc = clang_getLocationForOffset(tu, file, 0); 76 CXSourceLocation lastLoc = clang_getLocationForOffset(tu, file, fileSize); 77 if (clang_equalLocations(topLoc, clang_getNullLocation()) 78 || clang_equalLocations(lastLoc, clang_getNullLocation())) { 79 writef("cannot retrieve location\n"); 80 throw new Exception("location"); 81 } 82 83 // make a range from locations 84 CXSourceRange range = clang_getRange(topLoc, lastLoc); 85 if (clang_Range_isNull(range)) { 86 writef("cannot retrieve range\n"); 87 throw new Exception("range"); 88 } 89 90 return range; 91 } 92 93 int tokenize(string filename) { 94 // create index w/ excludeDeclsFromPCH = 1, displayDiagnostics=1. 95 CXIndex index = clang_createIndex(1, 1); 96 97 // create Translation Unit 98 CXTranslationUnit tu = clang_parseTranslationUnit(index, 99 filename.toStringz, null, 0, null, 0, 0); 100 if (tu == null) { 101 writef("Cannot parse translation unit\n"); 102 return 1; 103 } 104 105 // get CXSouceRange of the file 106 CXSourceRange range = get_filerange(tu, filename); 107 108 // tokenize in the range 109 CXToken* tokens; 110 uint numTokens; 111 clang_tokenize(tu, range, &tokens, &numTokens); 112 113 // show tokens 114 showAllTokens(tu, tokens, numTokens); 115 116 clang_disposeTokens(tu, tokens, numTokens); 117 clang_disposeTranslationUnit(tu); 118 clang_disposeIndex(index); 119 120 return 0; 121 } 122 123 int dumpAst(string filename, string[] flags) { 124 import cpptooling.analyzer.clang.context; 125 import clang.TranslationUnit : dumpAST; 126 127 auto ctx = ClangContext(Yes.useInternalHeaders, Yes.prependParamSyntaxOnly); 128 auto tu = ctx.makeTranslationUnit(filename, flags); 129 writeln(dumpAST(tu)); 130 131 return 0; 132 } 133 134 int dumpIncludes(string filename, string[] flags) { 135 import std.ascii; 136 import std.algorithm : filter; 137 import cpptooling.analyzer.clang.context; 138 import cpptooling.analyzer.clang.include_visitor; 139 import cpptooling.analyzer.clang.cursor_visitor; 140 141 auto ctx = ClangContext(Yes.useInternalHeaders, Yes.prependParamSyntaxOnly); 142 auto tu = ctx.makeTranslationUnit(filename, flags); 143 144 foreach (c; tu.cursor.visitBreathFirst.filter!(a => a.kind == CXCursorKind.inclusionDirective)) { 145 writefln(`#include "%s"%s %s`, c.spelling, newline, c.include.file); 146 } 147 148 return 0; 149 } 150 151 int dumpBody(string fname, string[] flags) { 152 import std.typecons : scoped, NullableRef; 153 import cpptooling.analyzer.clang.analyze_helper; 154 import cpptooling.analyzer.clang.ast; 155 import cpptooling.analyzer.clang.context; 156 import cpptooling.analyzer.clang.check_parse_result : hasParseErrors, 157 logDiagnostic; 158 import cpptooling.analyzer.clang.cursor_logger : logNode, mixinNodeLog; 159 import cpptooling.data.symbol : Container; 160 161 final class BodyVisitor : Visitor { 162 alias visit = Visitor.visit; 163 mixin generateIndentIncrDecr; 164 165 NullableRef!Container container; 166 167 this(ref Container container) { 168 this.container = &container; 169 } 170 171 override void visit(const(Declaration) v) { 172 mixin(mixinNodeLog!()); 173 v.accept(this); 174 } 175 176 override void visit(const(Statement) v) { 177 mixin(mixinNodeLog!()); 178 v.accept(this); 179 } 180 181 override void visit(const(Expression) v) { 182 mixin(mixinNodeLog!()); 183 v.accept(this); 184 } 185 186 override void visit(const(CallExpr) v) { 187 mixin(mixinNodeLog!()); 188 189 auto c_func = v.cursor.referenced; 190 logger.tracef("ref:'%s' kind:%s", c_func.spelling, c_func.kind.to!string()); 191 192 if (c_func.kind == CXCursorKind.functionDecl) { 193 auto result = analyzeFunctionDecl(c_func, container, indent); 194 () @trusted{ logger.trace("result: ", result); }(); 195 } else if (c_func.kind == CXCursorKind.cxxMethod) { 196 auto result = analyzeCxxMethod(c_func, container, indent); 197 () @trusted{ logger.trace("result: ", result); }(); 198 } else { 199 logger.trace("unknown callexpr: ", c_func.kind.to!string()); 200 } 201 202 v.accept(this); 203 } 204 205 override void visit(const(DeclRefExpr) v) { 206 mixin(mixinNodeLog!()); 207 208 auto c_ref = v.cursor.referenced; 209 logger.trace("ref: ", c_ref.spelling, " kind:", c_ref.kind.to!string()); 210 211 v.accept(this); 212 } 213 214 override void visit(const(MemberRefExpr) v) { 215 mixin(mixinNodeLog!()); 216 217 auto c_ref = v.cursor.referenced; 218 logger.trace("ref: ", c_ref.spelling, " kind:", c_ref.kind.to!string()); 219 220 v.accept(this); 221 } 222 } 223 224 final class MainVisitor : Visitor { 225 alias visit = Visitor.visit; 226 mixin generateIndentIncrDecr; 227 228 Container container; 229 230 override void visit(const(TranslationUnit) v) { 231 mixin(mixinNodeLog!()); 232 v.accept(this); 233 } 234 235 override void visit(const(ClassDecl) v) { 236 mixin(mixinNodeLog!()); 237 v.accept(this); 238 } 239 240 override void visit(const(StructDecl) v) { 241 mixin(mixinNodeLog!()); 242 v.accept(this); 243 } 244 245 override void visit(const(FunctionDecl) v) @trusted { 246 mixin(mixinNodeLog!()); 247 248 auto visitor = scoped!(BodyVisitor)(container); 249 visitor.indent = indent; 250 v.accept(visitor); 251 } 252 253 override void visit(const(CxxMethod) v) @trusted { 254 mixin(mixinNodeLog!()); 255 256 auto visitor = scoped!(BodyVisitor)(container); 257 visitor.indent = indent; 258 v.accept(visitor); 259 } 260 } 261 262 auto ctx = ClangContext(Yes.useInternalHeaders, Yes.prependParamSyntaxOnly); 263 auto tu = ctx.makeTranslationUnit(fname, flags); 264 265 if (tu.hasParseErrors) { 266 logDiagnostic(tu); 267 } 268 269 auto visitor = new MainVisitor; 270 auto ast = ClangAST!(typeof(visitor))(tu.cursor); 271 ast.accept(visitor); 272 273 return 0; 274 } 275 276 int main(string[] args) { 277 import colorlog : VerboseMode, confLogger; 278 279 confLogger(VerboseMode.trace); 280 281 if (args.length < 3) { 282 writeln("devtool <category> filename"); 283 writeln("categories: tok, dumpast, dumpbody, includes"); 284 return 1; 285 } 286 287 string[] flags; 288 if (args.length > 3) { 289 flags = args[3 .. $]; 290 } 291 292 showClangVersion(); 293 294 switch (args[1]) { 295 case "tok": 296 return tokenize(args[2]); 297 case "dumpast": 298 return dumpAst(args[2], flags); 299 case "dumpbody": 300 return dumpBody(args[2], flags); 301 case "includes": 302 return dumpIncludes(args[2], flags); 303 default: 304 return 1; 305 } 306 }