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