1 /**
2 Date: 2015-2021, Joakim Brännström
3 License: MPL-2, Mozilla Public License 2.0
4 Author: Joakim Brännström (joakim.brannstrom@gmx.com)
5 */
6 module libclang_ast.context;
7 
8 import std.typecons : Flag;
9 import logger = std.experimental.logger;
10 
11 import clang.c.Index : CXUnsavedFile;
12 
13 public import my.path : Path;
14 public import blob_model : BlobVfs;
15 
16 version (unittest) {
17     import unit_threaded : Name, shouldEqual;
18 }
19 
20 @safe:
21 
22 /** Convenient context of items needed to practically create a clang AST.
23  *
24  * "Creating a clang AST" means calling $(D makeTranslationUnit).
25  *
26  * Assumes that the scope of a ClangContext is the same as that stated for a
27  * clang Index. Namely that those translation units that are created are the
28  * same as those that would be linked together into an executable or library.
29  *
30  * Items are:
31  *  - An index that all translation units use as input.
32  *  - A VFS providing access to the files that the translation unites are
33  *    derived from.
34  */
35 struct ClangContext {
36     import clang.Index : Index;
37     import clang.TranslationUnit : TranslationUnit;
38 
39     import blob_model : BlobVfs, Uri, Blob;
40 
41     import clang.c.Index : CXTranslationUnit_Flags;
42 
43     private {
44         Index index;
45         string[] internal_header_arg;
46         string[] syntax_only_arg;
47     }
48 
49     /** Access to the virtual filesystem used when instantiating translation
50      * units.
51      */
52     BlobVfs vfs;
53 
54     @disable this();
55 
56     // The context is "heavy" therefor disabling moving.
57     @disable this(this);
58 
59     /** Create an instance.
60      *
61      * The binary dextool has clang specified headers attached. Those are feed
62      * to the VFS and used when the flag useInternalHeaders is "yes". To make
63      * them accessable a "-I" parameter with their in-memory location is
64      * supplied to all instantiated translation units.
65      *
66      * TODO from llvm-6.0 -fsyntax-only is default and ignored. The
67      * functionality to prepend with -fsyntax-only should thus be removed.
68      *
69      * Params:
70      *   useInternalHeaders = load the VFS with in-memory system headers.
71      *   prependParamSyntaxOnly = prepend the flag -fsyntax-only to instantiated translation units.
72      */
73     this(Flag!"useInternalHeaders" useInternalHeaders,
74             Flag!"prependParamSyntaxOnly" prependParamSyntaxOnly) @trusted {
75         this.index = Index(false, false);
76         this.vfs = new BlobVfs;
77 
78         if (useInternalHeaders) {
79             import clang.Compiler : Compiler;
80 
81             Compiler compiler;
82             this.internal_header_arg = compiler.extraIncludeFlags;
83             foreach (hdr; compiler.extraHeaders) {
84                 auto f = vfs.open(new Blob(Uri(hdr.filename), hdr.content));
85             }
86         }
87 
88         if (prependParamSyntaxOnly) {
89             this.syntax_only_arg = ["-fsyntax-only"];
90         }
91     }
92 
93     /** Create a translation unit from the context.
94      *
95      * The translation unit is NOT kept by the context.
96      */
97     auto makeTranslationUnit(in string sourceFilename, in string[] commandLineArgs = null,
98             uint options = CXTranslationUnit_Flags.detailedPreprocessingRecord) @safe {
99         import std.array : join;
100 
101         auto prependDefaultFlags(string[] in_cflags) {
102             import std.algorithm : canFind;
103 
104             if (in_cflags.canFind(syntax_only_arg)) {
105                 return in_cflags;
106             } else {
107                 return syntax_only_arg ~ in_cflags;
108             }
109         }
110 
111         debug logger.trace(sourceFilename);
112         debug logger.trace("Compiler flags: ", commandLineArgs.join(" "));
113 
114         string[] args = prependDefaultFlags(commandLineArgs ~ internal_header_arg);
115 
116         debug logger.trace("Internal compiler flags: ", args.join(" "));
117 
118         const uri = Uri(sourceFilename);
119 
120         // ensure the file exist in the filesys layer.
121         // it has either been added as an in-memory file by the user or it is
122         // read from the filesystem.
123         if (!vfs.exists(uri)) {
124             vfs.openFromFile(uri);
125         }
126 
127         auto files = vfs.toClangFiles;
128 
129         return TranslationUnit.parse(index, sourceFilename, args, files);
130     }
131 }
132 
133 @("shall be an instance")
134 @system unittest {
135     import std.typecons : Yes;
136 
137     auto ctx = ClangContext(Yes.useInternalHeaders, Yes.prependParamSyntaxOnly);
138 }
139 
140 /** Convert to an array that can be passed on to clang to use as in-memory source code.
141  *
142  * Trusted: operates on files handled by a VirtualFileSystem that ensues that
143  * they exists. The VFS has taken care of validating the files.
144  */
145 CXUnsavedFile[] toClangFiles(ref BlobVfs vfs) @trusted {
146     import std.algorithm : map;
147     import std.array : array;
148     import std..string : toStringz;
149 
150     return vfs.uris.map!((a) {
151         auto s = vfs.get(a).content[];
152         auto fname = (cast(string) a).toStringz;
153         return CXUnsavedFile(fname, cast(char*) s.ptr, s.length);
154     }).array();
155 }