1 /**
2 Copyright: Copyright (c) 2016-2021, Joakim Brännström. All rights reserved.
3 License: MPL-2
4 Author: Joakim Brännström (joakim.brannstrom@gmx.com)
5 
6 This Source Code Form is subject to the terms of the Mozilla Public License,
7 v.2.0. If a copy of the MPL was not distributed with this file, You can obtain
8 one at http://mozilla.org/MPL/2.0/.
9 */
10 module libclang_ast.ast.tree;
11 
12 import logger = std.experimental.logger;
13 
14 import clang.Cursor : Cursor;
15 
16 import clang.c.Index : CXCursorKind;
17 
18 import libclang_ast.ast.attribute;
19 import libclang_ast.ast.declaration;
20 import libclang_ast.ast.directive;
21 import libclang_ast.ast.expression;
22 import libclang_ast.ast.extra;
23 import libclang_ast.ast.preprocessor;
24 import libclang_ast.ast.reference;
25 import libclang_ast.ast.statement;
26 import libclang_ast.ast.translationunit;
27 
28 import libclang_ast.ast.nodes : makeNodeClassName;
29 
30 version (unittest) {
31     import unit_threaded : shouldEqual, shouldBeTrue;
32 }
33 
34 /**
35  * Wrap a clang cursor. No restrictions on what type of cursor it is.
36  * Accept a Visitor.
37  * During accept traverse the AST to:
38  *  - wrap Cursors's in specific classes corresponding to the kind of cursor.
39  *  - call Visitor's visit(...) with wrapped cursor.
40  *
41  * Starts traversing the AST from the root.
42  *
43  * Optional functions:
44  *   void incr(). Called before descending a node.
45  *   void decr(). Called after ascending a node.
46  */
47 struct ClangAST(VisitorT) {
48     import std.traits : isArray;
49     import clang.Cursor : Cursor;
50 
51     Cursor root;
52 
53     void accept(ref VisitorT visitor) @safe {
54         dispatch(root, visitor);
55     }
56 }
57 
58 /** Apply the visitor to the children of the cursor.
59  *
60  * Optional functions:
61  *   void incr(). Called before descending a node.
62  *   void decr(). Called after ascending a node.
63  */
64 void accept(VisitorT)(ref const(Cursor) cursor, ref VisitorT visitor) @safe {
65     import clang.Visitor : Visitor;
66 
67     visitor.incr();
68     scope (exit)
69         visitor.decr();
70 
71     () @trusted {
72         foreach (child, _; Visitor(cursor)) {
73             dispatch(child, visitor);
74         }
75     }();
76 }
77 
78 /** Static wrapping of the cursor followed by a passing it to the visitor.
79  *
80  * The cursor is wrapped in the class that corresponds to the kind of the
81  * cursor.
82  *
83  * Note that the mixins shall be ordered alphabetically.
84  */
85 void dispatch(VisitorT)(ref const(Cursor) cursor, VisitorT visitor) @safe {
86     import clang.Visitor : Visitor;
87     import libclang_ast.ast.nodes;
88     import std.conv : to;
89 
90     // expecting ignoreCursors to be dextool.set.Set.
91     static if (__traits(hasMember, VisitorT, "ignoreCursors")) {
92         const h = cursor.toHash;
93         if (h in visitor.ignoreCursors) {
94             visitor.ignoreCursors.remove(h);
95             () @trusted {
96                 foreach (child, _; Visitor(cursor)) {
97                     dispatch(child, visitor);
98                 }
99             }();
100             return;
101         }
102     }
103 
104     switch (cursor.kind) {
105         mixin(wrapCursor!(visitor, cursor)(AttributeSeq));
106         mixin(wrapCursor!(visitor, cursor)(DeclarationSeq));
107         mixin(wrapCursor!(visitor, cursor)(DirectiveSeq));
108         mixin(wrapCursor!(visitor, cursor)(ExpressionSeq));
109         mixin(wrapCursor!(visitor, cursor)(ExtraSeq));
110         mixin(wrapCursor!(visitor, cursor)(PreprocessorSeq));
111         mixin(wrapCursor!(visitor, cursor)(ReferenceSeq));
112         mixin(wrapCursor!(visitor, cursor)(StatementSeq));
113         mixin(wrapCursor!(visitor, cursor)(TranslationUnitSeq));
114 
115     default:
116         debug logger.trace("Node not handled:", to!string(cursor.kind));
117     }
118 }
119 
120 private:
121 
122 string wrapCursor(alias visitor, alias cursor)(immutable(string)[] cases) {
123     import std.format : format;
124 
125     //TODO allocate in an allocator, not GC with "new"
126     string result;
127 
128     foreach (case_; cases) {
129         result ~= format("case CXCursorKind.%s: auto wrapped = new %s(%s); %s.visit(wrapped); break;\n",
130                 case_, makeNodeClassName(case_), cursor.stringof, visitor.stringof);
131     }
132     return result;
133 }
134 
135 @("shall generate code for a case block that wraps a libclang Cursor in the correct class")
136 unittest {
137     import std.conv : to;
138 
139     enum Dummy {
140         xCase1,
141         xCase2
142     }
143 
144     int visitor;
145     int cursor;
146 
147     wrapCursor!(visitor, cursor)(["Dummy.xCase1", "Dummy.xCase2"]).shouldEqual(
148             "case Dummy.xCase1: auto wrapped = new Case1(cursor); visitor.visit(wrapped); break;
149 case Dummy.xCase2: auto wrapped = new Case2(cursor); visitor.visit(wrapped); break;
150 ");
151 }
152 
153 @("shall route nodes visits to the base node of each group")
154 @safe unittest {
155     import libclang_ast.ast : Visitor;
156     import libclang_ast.ast.nodes;
157 
158     final class TestVisitor : Visitor {
159         alias visit = Visitor.visit;
160 
161         bool attribute;
162         override void visit(const(Attribute)) {
163             attribute = true;
164         }
165 
166         bool declaration;
167         override void visit(const(Declaration)) {
168             declaration = true;
169         }
170 
171         bool directive;
172         override void visit(const(Directive)) {
173             directive = true;
174         }
175 
176         bool expression;
177         override void visit(const(Expression)) {
178             expression = true;
179         }
180 
181         bool extra;
182         override void visit(const(Extra)) {
183             extra = true;
184         }
185 
186         bool preprocessor;
187         override void visit(const(Preprocessor)) {
188             preprocessor = true;
189         }
190 
191         bool reference;
192         override void visit(const(Reference)) {
193             reference = true;
194         }
195 
196         bool statement;
197         override void visit(const(Statement)) {
198             statement = true;
199         }
200 
201         bool translationunit;
202         override void visit(const(TranslationUnit)) {
203             translationunit = true;
204         }
205     }
206 
207     auto test = new TestVisitor;
208 
209     Cursor cursor;
210 
211     foreach (kind; [
212             __traits(getMember, CXCursorKind, AttributeSeq[0]),
213             __traits(getMember, CXCursorKind, DeclarationSeq[0]),
214             __traits(getMember, CXCursorKind, DirectiveSeq[0]),
215             __traits(getMember, CXCursorKind, ExpressionSeq[0]),
216             __traits(getMember, CXCursorKind, ExtraSeq[0]),
217             __traits(getMember, CXCursorKind, PreprocessorSeq[0]),
218             __traits(getMember, CXCursorKind, ReferenceSeq[0]),
219             __traits(getMember, CXCursorKind, StatementSeq[0]),
220             __traits(getMember, CXCursorKind, TranslationUnitSeq[0])
221         ]) {
222         switch (kind) {
223             mixin(wrapCursor!(test, cursor)(AttributeSeq));
224             mixin(wrapCursor!(test, cursor)(DeclarationSeq));
225             mixin(wrapCursor!(test, cursor)(DirectiveSeq));
226             mixin(wrapCursor!(test, cursor)(ExpressionSeq));
227             mixin(wrapCursor!(test, cursor)(ExtraSeq));
228             mixin(wrapCursor!(test, cursor)(PreprocessorSeq));
229             mixin(wrapCursor!(test, cursor)(ReferenceSeq));
230             mixin(wrapCursor!(test, cursor)(StatementSeq));
231             mixin(wrapCursor!(test, cursor)(TranslationUnitSeq));
232         default:
233             assert(0);
234         }
235     }
236 
237     test.attribute.shouldBeTrue;
238     test.declaration.shouldBeTrue;
239     test.directive.shouldBeTrue;
240     test.expression.shouldBeTrue;
241     test.extra.shouldBeTrue;
242     test.preprocessor.shouldBeTrue;
243     test.reference.shouldBeTrue;
244     test.statement.shouldBeTrue;
245     test.translationunit.shouldBeTrue;
246 }