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 8 module clang.TranslationUnit; 9 10 import std..string; 11 12 import clang.c.Index; 13 14 import clang.Cursor; 15 import clang.Diagnostic; 16 import clang.File; 17 import clang.Index; 18 import clang.SourceLocation; 19 import clang.SourceRange; 20 import clang.Visitor; 21 22 /** A single translation unit, which resides in an index. 23 */ 24 struct TranslationUnit { 25 import std.typecons : Nullable; 26 import clang.Util; 27 28 static private struct ContainTU { 29 mixin CX!("TranslationUnit"); 30 31 ~this() @safe { 32 dispose(); 33 } 34 } 35 36 ContainTU cx; 37 alias cx this; 38 39 // the translation unit is destroyed when the dtor is ran. 40 @disable this(this); 41 42 /** 43 * Trusted: on the assumption that clang_parseTranslationUnit is 44 * implemented by the LLVM team. 45 */ 46 static TranslationUnit parse(ref Index index, string sourceFilename, string[] commandLineArgs, 47 CXUnsavedFile[] unsavedFiles = null, 48 uint options = CXTranslationUnit_Flags.detailedPreprocessingRecord) @trusted { 49 50 // dfmt off 51 // Trusted: on the assumption that the LLVM team are competent. That 52 // any problems that exist would have been found by now. 53 auto p = clang_parseTranslationUnit( 54 index.cx, 55 sourceFilename.toStringz, 56 strToCArray(commandLineArgs), 57 cast(int) commandLineArgs.length, 58 toCArray!(CXUnsavedFile)(unsavedFiles), 59 cast(uint) unsavedFiles.length, 60 options 61 ); 62 // dfmt on 63 64 return TranslationUnit(p); 65 } 66 67 /** Convenient function to create a TranslationUnit from source code via a 68 * parameter. 69 * 70 * Common use cases is unit testing. 71 * 72 * Trusted: on the assumption that 73 * clang.TranslationUnit.TranslationUnit.~this is correctly implemented. 74 */ 75 static TranslationUnit parseString(ref Index index, string source, string[] commandLineArgs, 76 CXUnsavedFile[] unsavedFiles = null, 77 uint options = CXTranslationUnit_Flags.detailedPreprocessingRecord) @trusted { 78 import std..string : toStringz; 79 80 string path = randomSourceFileName; 81 CXUnsavedFile file; 82 if (source.length == 0) { 83 file = CXUnsavedFile(path.toStringz, null, source.length); 84 } else { 85 file = CXUnsavedFile(path.toStringz, &source[0], source.length); 86 } 87 88 auto in_memory_files = unsavedFiles ~ [file]; 89 90 auto translationUnit = TranslationUnit.parse(index, path, 91 commandLineArgs, in_memory_files, options); 92 93 return translationUnit; 94 } 95 96 private static string randomSourceFileName() @safe { 97 import std.traits : fullyQualifiedName; 98 import std.path : buildPath; 99 100 static string virtualPath() { 101 version (Windows) 102 enum root = `C:\`; 103 104 else 105 enum root = "/"; 106 107 import std.conv : text; 108 import std.random; 109 110 return buildPath(root, text(uniform(1, 10_000_000))); 111 } 112 113 auto s = "random_source_filename.h"; 114 return buildPath(virtualPath, s); 115 } 116 117 package this(CXTranslationUnit cx) { 118 this.cx = ContainTU(cx); 119 } 120 121 bool isValid() @trusted { 122 return cx.isValid; 123 } 124 125 /** 126 * Trusted: on the assumption that accessing the payload of the refcounted 127 * TranslationUnit is @safe. 128 */ 129 @property DiagnosticVisitor diagnostics() @trusted { 130 return DiagnosticVisitor(cx); 131 } 132 133 @property DiagnosticSet diagnosticSet() { 134 return DiagnosticSet(clang_getDiagnosticSetFromTU(cx)); 135 } 136 137 @property size_t numDiagnostics() { 138 return clang_getNumDiagnostics(cx); 139 } 140 141 @property DeclarationVisitor declarations() { 142 auto c = Cursor(clang_getTranslationUnitCursor(cx)); 143 return DeclarationVisitor(c); 144 } 145 146 Nullable!File file(string filename) @trusted { 147 Nullable!File r; 148 149 auto f = clang_getFile(cx, filename.toStringz); 150 if (f !is null) { 151 r = File(f); 152 } 153 154 return r; 155 } 156 157 File file() @safe { 158 return file(spelling); 159 } 160 161 @property string spelling() @trusted { 162 return toD(clang_getTranslationUnitSpelling(cx)); 163 } 164 165 /** 166 * Trusted: on the assumption that the LLVM team is superb programmers. 167 */ 168 @property Cursor cursor() @trusted { 169 auto r = clang_getTranslationUnitCursor(cx); 170 return Cursor(r); 171 } 172 173 SourceLocation location(uint offset) @trusted { 174 CXFile file = clang_getFile(cx, spelling.toStringz); 175 return SourceLocation(clang_getLocationForOffset(cx, file, offset)); 176 } 177 178 SourceLocation location(string path, uint offset) @trusted { 179 CXFile file = clang_getFile(cx, path.toStringz); 180 return SourceLocation(clang_getLocationForOffset(cx, file, offset)); 181 } 182 183 SourceRange extent(uint startOffset, uint endOffset) { 184 CXFile file = clang_getFile(cx, spelling.toStringz); 185 auto start = clang_getLocationForOffset(cx, file, startOffset); 186 auto end = clang_getLocationForOffset(cx, file, endOffset); 187 return SourceRange(clang_getRange(start, end)); 188 } 189 190 package SourceLocation[] includeLocationsImpl(Range)(Range cursors) @safe { 191 // `cursors` range should at least contain all global 192 // preprocessor cursors, although it can contain more. 193 194 bool[string] stacked; 195 bool[string] included; 196 SourceLocation[] locationStack; 197 SourceLocation[] locations = [location("", 0), location(file.name, 0)]; 198 199 foreach (idx, cursor; cursors) { 200 if (cursor.kind == CXCursorKind.inclusionDirective) { 201 auto path = cursor.location.spelling.file.name; 202 auto ptr = path in stacked; 203 204 if (ptr !is null && *ptr) { 205 while (locationStack[$ - 1].path != path) { 206 stacked[locationStack[$ - 1].path] = false; 207 locations ~= locationStack[$ - 1]; 208 locationStack = locationStack[0 .. $ - 1]; 209 } 210 211 stacked[path] = false; 212 locations ~= locationStack[$ - 1]; 213 locationStack = locationStack[0 .. $ - 1]; 214 } 215 216 if ((cursor.include.file.name in included) is null) { 217 locationStack ~= cursor.extent.end; 218 stacked[path] = true; 219 locations ~= location(cursor.include.file.name, 0); 220 included[cursor.include.file.name] = true; 221 } 222 } 223 } 224 225 while (locationStack.length != 0) { 226 locations ~= locationStack[$ - 1]; 227 locationStack = locationStack[0 .. $ - 1]; 228 } 229 230 return locations; 231 } 232 233 SourceLocation[] includeLocations() { 234 return includeLocationsImpl(cursor.all); 235 } 236 237 package ulong delegate(SourceLocation) relativeLocationAccessorImpl(Range)(Range cursors) { 238 // `cursors` range should at least contain all global 239 // preprocessor cursors, although it can contain more. 240 241 SourceLocation[] locations = includeLocationsImpl(cursors); 242 243 struct Entry { 244 uint index; 245 SourceLocation location; 246 247 int opCmp(ref const Entry s) const { 248 return location.offset < s.location.offset ? -1 : 1; 249 } 250 251 int opCmp(ref const SourceLocation s) const { 252 return location.offset < s.offset + 1 ? -1 : 1; 253 } 254 } 255 256 Entry[][string] map; 257 258 foreach (uint index, location; locations) 259 map[location.path] ~= Entry(index, location); 260 261 uint findIndex(SourceLocation a) { 262 auto entries = map[a.path]; 263 264 import std.range; 265 266 auto lower = assumeSorted(entries).lowerBound(a); 267 268 return lower.empty ? 0 : lower.back.index; 269 } 270 271 ulong accessor(SourceLocation location) { 272 return ((cast(ulong) findIndex(location)) << 32) | (cast(ulong) location.offset); 273 } 274 275 return &accessor; 276 } 277 278 size_t delegate(SourceLocation) relativeLocationAccessor() { 279 return relativeLocationAccessorImpl(cursor.all); 280 } 281 } 282 283 string dumpAST(ref TranslationUnit tu, bool skipIncluded = false) { 284 import std.array : appender; 285 import clang.Cursor : dumpAST; 286 287 auto result = appender!string(); 288 auto c = tu.cursor; 289 290 if (skipIncluded) { 291 File file = tu.file; 292 dumpAST(c, result, 0, &file); 293 } else { 294 dumpAST(c, result, 0); 295 } 296 297 return result.data; 298 } 299 300 struct DiagnosticVisitor { 301 private CXTranslationUnit translatoinUnit; 302 303 this(CXTranslationUnit translatoinUnit) @safe { 304 this.translatoinUnit = translatoinUnit; 305 } 306 307 size_t length() { 308 return clang_getNumDiagnostics(translatoinUnit); 309 } 310 311 int opApply(int delegate(ref Diagnostic) dg) { 312 int result; 313 314 foreach (i; 0 .. length) { 315 auto diag = clang_getDiagnostic(translatoinUnit, cast(uint) i); 316 auto dDiag = Diagnostic(diag); 317 result = dg(dDiag); 318 319 if (result) 320 break; 321 } 322 323 return result; 324 } 325 }