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(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)(const(Cursor) cursor, 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)(const(Cursor) cursor, VisitorT visitor) @safe {
86     import clang.Visitor : Visitor;
87     import libclang_ast.ast.nodes;
88     import std.conv : to;
89 
90     static if (__traits(hasMember, VisitorT, "ignoreCursors")) {
91         const h = cursor.toHash;
92         if (h in visitor.ignoreCursors) {
93             visitor.ignoreCursors.remove(h);
94             () @trusted {
95                 foreach (child, _; Visitor(cursor)) {
96                     dispatch(child, visitor);
97                 }
98             }();
99             return;
100         }
101     }
102 
103     switch (cursor.kind) {
104         mixin(wrapCursor!(visitor, cursor)(AttributeSeq));
105         mixin(wrapCursor!(visitor, cursor)(DeclarationSeq));
106         mixin(wrapCursor!(visitor, cursor)(DirectiveSeq));
107         mixin(wrapCursor!(visitor, cursor)(ExpressionSeq));
108         mixin(wrapCursor!(visitor, cursor)(ExtraSeq));
109         mixin(wrapCursor!(visitor, cursor)(PreprocessorSeq));
110         mixin(wrapCursor!(visitor, cursor)(ReferenceSeq));
111         mixin(wrapCursor!(visitor, cursor)(StatementSeq));
112         mixin(wrapCursor!(visitor, cursor)(TranslationUnitSeq));
113 
114     default:
115         debug logger.trace("Node not handled:", to!string(cursor.kind));
116     }
117 }
118 
119 private:
120 
121 string wrapCursor(alias visitor, alias cursor)(immutable(string)[] cases) {
122     import std.format : format;
123 
124     string result;
125 
126     foreach (case_; cases) {
127         result ~= format("case CXCursorKind.%s: scope wrapped = new %s(%s); %s.visit(wrapped); break;\n",
128                 case_, makeNodeClassName(case_), cursor.stringof, visitor.stringof);
129     }
130     return result;
131 }
132 
133 @("shall generate code for a case block that wraps a libclang Cursor in the correct class")
134 unittest {
135     import std.conv : to;
136 
137     enum Dummy {
138         xCase1,
139         xCase2
140     }
141 
142     int visitor;
143     int cursor;
144 
145     wrapCursor!(visitor, cursor)(["Dummy.xCase1", "Dummy.xCase2"]).shouldEqual(
146             "case Dummy.xCase1: scope wrapped = new Case1(cursor); visitor.visit(wrapped); break;
147 case Dummy.xCase2: scope wrapped = new Case2(cursor); visitor.visit(wrapped); break;
148 ");
149 }
150 
151 @("shall route nodes visits to the base node of each group")
152 @safe unittest {
153     import libclang_ast.ast : Visitor;
154     import libclang_ast.ast.nodes;
155 
156     final class TestVisitor : Visitor {
157         alias visit = Visitor.visit;
158 
159         bool attribute;
160         override void visit(const(Attribute)) {
161             attribute = true;
162         }
163 
164         bool declaration;
165         override void visit(const(Declaration)) {
166             declaration = true;
167         }
168 
169         bool directive;
170         override void visit(const(Directive)) {
171             directive = true;
172         }
173 
174         bool expression;
175         override void visit(const(Expression)) {
176             expression = true;
177         }
178 
179         bool extra;
180         override void visit(const(Extra)) {
181             extra = true;
182         }
183 
184         bool preprocessor;
185         override void visit(const(Preprocessor)) {
186             preprocessor = true;
187         }
188 
189         bool reference;
190         override void visit(const(Reference)) {
191             reference = true;
192         }
193 
194         bool statement;
195         override void visit(const(Statement)) {
196             statement = true;
197         }
198 
199         bool translationunit;
200         override void visit(const(TranslationUnit)) {
201             translationunit = true;
202         }
203     }
204 
205     auto test = new TestVisitor;
206 
207     Cursor cursor;
208 
209     foreach (kind; [
210             __traits(getMember, CXCursorKind, AttributeSeq[0]),
211             __traits(getMember, CXCursorKind, DeclarationSeq[0]),
212             __traits(getMember, CXCursorKind, DirectiveSeq[0]),
213             __traits(getMember, CXCursorKind, ExpressionSeq[0]),
214             __traits(getMember, CXCursorKind, ExtraSeq[0]),
215             __traits(getMember, CXCursorKind, PreprocessorSeq[0]),
216             __traits(getMember, CXCursorKind, ReferenceSeq[0]),
217             __traits(getMember, CXCursorKind, StatementSeq[0]),
218             __traits(getMember, CXCursorKind, TranslationUnitSeq[0])
219         ]) {
220         switch (kind) {
221             mixin(wrapCursor!(test, cursor)(AttributeSeq));
222             mixin(wrapCursor!(test, cursor)(DeclarationSeq));
223             mixin(wrapCursor!(test, cursor)(DirectiveSeq));
224             mixin(wrapCursor!(test, cursor)(ExpressionSeq));
225             mixin(wrapCursor!(test, cursor)(ExtraSeq));
226             mixin(wrapCursor!(test, cursor)(PreprocessorSeq));
227             mixin(wrapCursor!(test, cursor)(ReferenceSeq));
228             mixin(wrapCursor!(test, cursor)(StatementSeq));
229             mixin(wrapCursor!(test, cursor)(TranslationUnitSeq));
230         default:
231             assert(0);
232         }
233     }
234 
235     test.attribute.shouldBeTrue;
236     test.declaration.shouldBeTrue;
237     test.directive.shouldBeTrue;
238     test.expression.shouldBeTrue;
239     test.extra.shouldBeTrue;
240     test.preprocessor.shouldBeTrue;
241     test.reference.shouldBeTrue;
242     test.statement.shouldBeTrue;
243     test.translationunit.shouldBeTrue;
244 }