1 /** 2 * Copyright: Copyright (c) 2011 Jacob Carlborg. All rights reserved. 3 * Authors: Jacob Carlborg 4 * Version: Initial created: Oct 1, 2011 5 * License: $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost Software License 1.0) 6 */ 7 module clang.TranslationUnit; 8 9 import std..string; 10 11 import clang.c.Index; 12 import clang.Cursor; 13 import clang.Diagnostic; 14 import clang.File; 15 import clang.Index; 16 import clang.SourceLocation; 17 import clang.SourceRange; 18 import clang.Token; 19 import clang.Util; 20 import clang.Visitor; 21 22 struct TranslationUnit 23 { 24 mixin CX; 25 26 string sourceString = null; 27 28 static TranslationUnit parse ( 29 Index index, 30 string sourceFilename, 31 const string[] commandLineArgs = ["-Wno-missing-declarations"], 32 CXUnsavedFile[] unsavedFiles = null, 33 uint options = CXTranslationUnit_Flags.detailedPreprocessingRecord) 34 { 35 string[] arguments = commandLineArgs.dup; 36 37 auto version_ = clangVersion(); 38 39 if (version_.major == 3 && version_.minor == 7) 40 arguments ~= "-D__int64=long long"; 41 42 return TranslationUnit( 43 clang_parseTranslationUnit( 44 index.cx, 45 sourceFilename.toStringz, 46 strToCArray(arguments), 47 cast(int) arguments.length, 48 toCArray!(CXUnsavedFile)(unsavedFiles), 49 cast(uint) unsavedFiles.length, 50 options)); 51 } 52 53 static TranslationUnit parseString ( 54 Index index, 55 string source, 56 string[] commandLineArgs = ["-Wno-missing-declarations"], 57 CXUnsavedFile[] unsavedFiles = null, 58 uint options = CXTranslationUnit_Flags.detailedPreprocessingRecord) 59 { 60 import std.file; 61 62 auto file = namedTempFile("dstep", ".h"); 63 auto name = file.name(); 64 file.write(source); 65 file.flush(); 66 file.detach(); 67 68 auto translationUnit = TranslationUnit.parse( 69 index, 70 name, 71 commandLineArgs, 72 unsavedFiles, 73 options); 74 75 remove(name); 76 77 translationUnit.sourceString = source; 78 79 return translationUnit; 80 } 81 82 package this (CXTranslationUnit cx) 83 { 84 this.cx = cx; 85 } 86 87 @property DiagnosticVisitor diagnostics () 88 { 89 return DiagnosticVisitor(cx); 90 } 91 92 @property DiagnosticSet diagnosticSet () 93 { 94 return DiagnosticSet(clang_getDiagnosticSetFromTU(cx)); 95 } 96 97 @property size_t numDiagnostics () 98 { 99 return clang_getNumDiagnostics(cx); 100 } 101 102 bool isCompiled() 103 { 104 import std.algorithm; 105 106 alias predicate = 107 x => x.severity != CXDiagnosticSeverity.error && 108 x.severity != CXDiagnosticSeverity.fatal; 109 110 return diagnosticSet.all!predicate(); 111 } 112 113 @property DeclarationVisitor declarations () 114 { 115 return DeclarationVisitor(clang_getTranslationUnitCursor(cx)); 116 } 117 118 File file (string filename) 119 { 120 return File(clang_getFile(cx, filename.toStringz)); 121 } 122 123 File file () 124 { 125 return file(spelling); 126 } 127 128 @property string spelling () 129 { 130 return toD(clang_getTranslationUnitSpelling(cx)); 131 } 132 133 @property string source () 134 { 135 import std.file : readText; 136 return sourceString ? sourceString : readText(spelling); 137 } 138 139 @property Cursor cursor () 140 { 141 auto r = clang_getTranslationUnitCursor(cx); 142 return Cursor(r); 143 } 144 145 SourceLocation location (uint offset) 146 { 147 CXFile file = clang_getFile(cx, spelling.toStringz); 148 return SourceLocation(clang_getLocationForOffset(cx, file, offset)); 149 } 150 151 SourceLocation location (string path, uint offset) 152 { 153 CXFile file = clang_getFile(cx, path.toStringz); 154 return SourceLocation(clang_getLocationForOffset(cx, file, offset)); 155 } 156 157 SourceRange extent (uint startOffset, uint endOffset) 158 { 159 CXFile file = clang_getFile(cx, spelling.toStringz); 160 auto start = clang_getLocationForOffset(cx, file, startOffset); 161 auto end = clang_getLocationForOffset(cx, file, endOffset); 162 return SourceRange(clang_getRange(start, end)); 163 } 164 165 package SourceLocation[] includeLocationsImpl(Range)(Range cursors) 166 { 167 // `cursors` range should at least contain all global 168 // preprocessor cursors, although it can contain more. 169 170 Set!string stacked; 171 Set!string included; 172 SourceLocation[] locationStack; 173 SourceLocation[] locations = [ location("", 0), location(file.name, 0) ]; 174 175 foreach (cursor; cursors) 176 { 177 if (cursor.kind == CXCursorKind.inclusionDirective) 178 { 179 auto ptr = cursor.path in stacked; 180 181 if (stacked.contains(cursor.path)) 182 { 183 while (locationStack[$ - 1].path != cursor.path) 184 { 185 stacked.remove(locationStack[$ - 1].path); 186 locations ~= locationStack[$ - 1]; 187 locationStack = locationStack[0 .. $ - 1]; 188 } 189 190 stacked.remove(cursor.path); 191 locations ~= locationStack[$ - 1]; 192 locationStack = locationStack[0 .. $ - 1]; 193 } 194 195 if ((cursor.includedPath in included) is null) 196 { 197 locationStack ~= cursor.extent.end; 198 stacked.add(cursor.path); 199 locations ~= location(cursor.includedPath, 0); 200 included.add(cursor.includedPath); 201 } 202 } 203 } 204 205 while (locationStack.length != 0) 206 { 207 locations ~= locationStack[$ - 1]; 208 locationStack = locationStack[0 .. $ - 1]; 209 } 210 211 return locations; 212 } 213 214 SourceLocation[] includeLocations() 215 { 216 return includeLocationsImpl(cursor.all); 217 } 218 219 package ulong delegate (SourceLocation) 220 relativeLocationAccessorImpl(Range)(Range cursors) 221 { 222 // `cursors` range should at least contain all global 223 // preprocessor cursors, although it can contain more. 224 225 SourceLocation[] locations = includeLocationsImpl(cursors); 226 227 struct Entry 228 { 229 uint index; 230 SourceLocation location; 231 232 int opCmp(ref const Entry s) const 233 { 234 return location.offset < s.location.offset ? -1 : 1; 235 } 236 237 int opCmp(ref const SourceLocation s) const 238 { 239 return location.offset < s.offset + 1 ? -1 : 1; 240 } 241 } 242 243 Entry[][string] map; 244 245 foreach (uint index, location; locations) 246 map[location.path] ~= Entry(index, location); 247 248 uint findIndex(SourceLocation a) 249 { 250 auto entries = map[a.path]; 251 252 import std.range; 253 254 auto lower = assumeSorted(entries).lowerBound(a); 255 256 return lower.empty ? 0 : lower.back.index; 257 } 258 259 ulong accessor(SourceLocation location) 260 { 261 return ((cast(ulong) findIndex(location)) << 32) | 262 (cast(ulong) location.offset); 263 } 264 265 return &accessor; 266 } 267 268 ulong delegate (SourceLocation) 269 relativeLocationAccessor() 270 { 271 return relativeLocationAccessorImpl(cursor.all); 272 } 273 274 bool delegate (SourceLocation, SourceLocation) 275 relativeLocationLessOp() 276 { 277 auto accessor = relativeLocationAccessor(); 278 279 bool lessOp(SourceLocation a, SourceLocation b) 280 { 281 if (a.file == b.file) 282 return a.offset < b.offset; 283 else 284 return accessor(a) < accessor(b); 285 } 286 287 return &lessOp; 288 } 289 290 bool delegate (Cursor, Cursor) 291 relativeCursorLocationLessOp() 292 { 293 auto accessor = relativeLocationAccessor(); 294 295 bool lessOp(Cursor a, Cursor b) 296 { 297 if (a.file == b.file) 298 return a.location.offset < b.location.offset; 299 else 300 return accessor(a.location) < accessor(b.location); 301 } 302 303 return &lessOp; 304 } 305 306 private struct TokenRange 307 { 308 CXTranslationUnit cx; 309 CXToken* tokens; 310 uint numTokens; 311 uint currentToken; 312 313 Token makeToken(CXToken token) 314 { 315 return Token( 316 clang_getTokenKind(token).toD, 317 clang_getTokenSpelling(cx, token).toD, 318 SourceRange(clang_getTokenExtent(cx, token))); 319 } 320 321 Token front() 322 { 323 return makeToken(tokens[currentToken]); 324 } 325 326 bool empty() 327 { 328 return numTokens == 0 || numTokens == currentToken; 329 } 330 331 void popFront() 332 { 333 currentToken++; 334 } 335 336 void dispose() 337 { 338 clang_disposeTokens(cx, tokens, numTokens); 339 } 340 } 341 342 private static TokenRange tokenizeImpl(CXTranslationUnit cx, SourceRange extent) 343 { 344 auto range = TokenRange(cx); 345 clang_tokenize(cx, extent.cx, &range.tokens, &range.numTokens); 346 return range; 347 } 348 349 package static Token[] tokenize(CXTranslationUnit cx, SourceRange extent) 350 { 351 import std.array : array; 352 import std.algorithm : stripRight; 353 354 auto range = tokenizeImpl(cx, extent); 355 auto tokens = range.array; 356 range.dispose(); 357 358 // For some reason libclang returns some tokens out of cursors extent.cursor 359 return tokens.stripRight!(token => !intersects(extent, token.extent)); 360 } 361 362 package static Token[] tokenizeNoComments(CXTranslationUnit cx, SourceRange extent) 363 { 364 import std.array : array; 365 import std.algorithm : filter, stripRight; 366 367 auto range = tokenizeImpl(cx, extent); 368 auto tokens = range.filter!(e => e.kind != TokenKind.comment).array; 369 range.dispose(); 370 371 // For some reason libclang returns some tokens out of cursors extent.cursor 372 return tokens.stripRight!(token => !intersects(extent, token.extent)); 373 } 374 375 Token[] tokenize(SourceRange extent) 376 { 377 return tokenize(cx, extent); 378 } 379 380 Token[] tokenizeNoComments(SourceRange extent) 381 { 382 return tokenizeNoComments(cx, extent); 383 } 384 385 Token[] tokens() 386 { 387 return tokenize(extent(0, cast(uint) source.length)); 388 } 389 390 Token[] tokensNoComments() 391 { 392 return tokenizeNoComments(extent(0, cast(uint) source.length)); 393 } 394 395 bool isFileMultipleIncludeGuarded(string path) 396 { 397 auto file = clang_getFile(cx, path.toStringz); 398 return clang_isFileMultipleIncludeGuarded(cx, file) != 0; 399 } 400 401 bool isMultipleIncludeGuarded() 402 { 403 return isFileMultipleIncludeGuarded(spelling); 404 } 405 406 string dumpAST(bool skipIncluded = true) 407 { 408 import std.array : appender; 409 410 auto result = appender!string(); 411 412 if (skipIncluded) 413 { 414 File file = this.file; 415 cursor.dumpAST(result, 0, &file); 416 } 417 else 418 { 419 cursor.dumpAST(result, 0); 420 } 421 422 return result.data; 423 } 424 } 425 426 struct DiagnosticVisitor 427 { 428 private CXTranslationUnit translatoinUnit; 429 430 this (CXTranslationUnit translatoinUnit) 431 { 432 this.translatoinUnit = translatoinUnit; 433 } 434 435 size_t length () 436 { 437 return clang_getNumDiagnostics(translatoinUnit); 438 } 439 440 int opApply (int delegate (ref Diagnostic) dg) 441 { 442 int result; 443 444 foreach (i ; 0 .. length) 445 { 446 auto diag = clang_getDiagnostic(translatoinUnit, cast(uint) i); 447 auto dDiag = Diagnostic(diag); 448 result = dg(dDiag); 449 450 if (result) 451 break; 452 } 453 454 return result; 455 } 456 } 457 458 Token[] tokenize(string source) 459 { 460 Index index = Index(false, false); 461 auto translUnit = TranslationUnit.parseString(index, source); 462 return translUnit.tokenize(translUnit.extent(0, cast(uint) source.length)); 463 } 464 465 Token[] tokenizeNoComments(string source) 466 { 467 Index index = Index(false, false); 468 auto translUnit = TranslationUnit.parseString(index, source); 469 return translUnit.tokenizeNoComments( 470 translUnit.extent(0, cast(uint) source.length)); 471 }