1 /**
2 Copyright: Copyright (c) 2016, 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 cpptooling.analyzer.clang.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 cpptooling.analyzer.clang.ast.attribute;
19 import cpptooling.analyzer.clang.ast.declaration;
20 import cpptooling.analyzer.clang.ast.directive;
21 import cpptooling.analyzer.clang.ast.expression;
22 import cpptooling.analyzer.clang.ast.extra;
23 import cpptooling.analyzer.clang.ast.preprocessor;
24 import cpptooling.analyzer.clang.ast.reference;
25 import cpptooling.analyzer.clang.ast.statement;
26 import cpptooling.analyzer.clang.ast.translationunit;
27 
28 import cpptooling.analyzer.clang.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         static if (isArray!VisitorT) {
55             foreach (v; visitor) {
56                 v.incr();
57             }
58 
59             scope (success) {
60                 foreach (v; visitor) {
61                     v.decr();
62                 }
63             }
64 
65         } else {
66             visitor.incr();
67             scope (success)
68                 visitor.decr();
69         }
70 
71         dispatch(root, visitor);
72     }
73 }
74 
75 /** Apply the visitor to the children of the cursor.
76  *
77  * Optional functions:
78  *   void incr(). Called before descending a node.
79  *   void decr(). Called after ascending a node.
80  */
81 void accept(VisitorT)(ref const(Cursor) cursor, ref VisitorT visitor) @safe {
82     import std.traits : isArray;
83     import clang.Visitor : Visitor;
84 
85     static if (isArray!VisitorT) {
86         foreach (v; visitor) {
87             v.incr();
88         }
89 
90         scope (success) {
91             foreach (v; visitor) {
92                 v.decr();
93             }
94         }
95 
96     } else {
97         visitor.incr();
98         scope (success)
99             visitor.decr();
100     }
101 
102     () @trusted{
103         foreach (child, _; Visitor(cursor)) {
104             dispatch(child, visitor);
105         }
106     }();
107 }
108 
109 /** Static wrapping of the cursor followed by a passing it to the visitor.
110  *
111  * The cursor is wrapped in the class that corresponds to the kind of the
112  * cursor.
113  *
114  * Note that the mixins shall be ordered alphabetically.
115  */
116 void dispatch(VisitorT)(ref const(Cursor) cursor, VisitorT visitor) @safe {
117     import cpptooling.analyzer.clang.ast.nodes;
118     import std.conv : to;
119 
120     switch (cursor.kind) {
121         mixin(wrapCursor!(visitor, cursor)(AttributeSeq));
122         mixin(wrapCursor!(visitor, cursor)(DeclarationSeq));
123         mixin(wrapCursor!(visitor, cursor)(DirectiveSeq));
124         mixin(wrapCursor!(visitor, cursor)(ExpressionSeq));
125         mixin(wrapCursor!(visitor, cursor)(ExtraSeq));
126         mixin(wrapCursor!(visitor, cursor)(PreprocessorSeq));
127         mixin(wrapCursor!(visitor, cursor)(ReferenceSeq));
128         mixin(wrapCursor!(visitor, cursor)(StatementSeq));
129         mixin(wrapCursor!(visitor, cursor)(TranslationUnitSeq));
130 
131     default:
132         debug logger.trace("Node not handled:", to!string(cursor.kind));
133     }
134 }
135 
136 private:
137 
138 string wrapCursor(alias visitor, alias cursor)(immutable(string)[] cases) {
139     import std.format : format;
140 
141     static if (is(typeof(visitor) : T[], T)) {
142         // is an array
143         enum visit = "foreach (v; " ~ visitor.stringof ~ ") { v.visit(wrapped); }";
144     } else {
145         enum visit = visitor.stringof ~ ".visit(wrapped);";
146     }
147 
148     //TODO allocate in an allocator, not GC with "new"
149     string result;
150 
151     foreach (case_; cases) {
152         result ~= format("case CXCursorKind.%s: auto wrapped = new %s(%s); %s break;\n",
153                 case_, makeNodeClassName(case_), cursor.stringof, visit);
154     }
155 
156     return result;
157 }
158 
159 @("shall generate code for a case block that wraps a libclang Cursor in the correct class")
160 unittest {
161     import std.conv : to;
162 
163     enum Dummy {
164         xCase1,
165         xCase2
166     }
167 
168     int visitor;
169     int cursor;
170 
171     wrapCursor!(visitor, cursor)(["Dummy.xCase1", "Dummy.xCase2"]).shouldEqual(
172             "case Dummy.xCase1: auto wrapped = new Case1(cursor); visitor.visit(wrapped); break;
173 case Dummy.xCase2: auto wrapped = new Case2(cursor); visitor.visit(wrapped); break;
174 ");
175 }
176 
177 @("shall route nodes visits to the base node of each group")
178 @safe unittest {
179     import cpptooling.analyzer.clang.ast : Visitor;
180     import cpptooling.analyzer.clang.ast.nodes;
181 
182     final class TestVisitor : Visitor {
183         alias visit = Visitor.visit;
184 
185         bool attribute;
186         override void visit(const(Attribute)) {
187             attribute = true;
188         }
189 
190         bool declaration;
191         override void visit(const(Declaration)) {
192             declaration = true;
193         }
194 
195         bool directive;
196         override void visit(const(Directive)) {
197             directive = true;
198         }
199 
200         bool expression;
201         override void visit(const(Expression)) {
202             expression = true;
203         }
204 
205         bool extra;
206         override void visit(const(Extra)) {
207             extra = true;
208         }
209 
210         bool preprocessor;
211         override void visit(const(Preprocessor)) {
212             preprocessor = true;
213         }
214 
215         bool reference;
216         override void visit(const(Reference)) {
217             reference = true;
218         }
219 
220         bool statement;
221         override void visit(const(Statement)) {
222             statement = true;
223         }
224 
225         bool translationunit;
226         override void visit(const(TranslationUnit)) {
227             translationunit = true;
228         }
229     }
230 
231     auto test = new TestVisitor;
232 
233     Cursor cursor;
234 
235     foreach (kind; [__traits(getMember, CXCursorKind, AttributeSeq[0]), __traits(getMember,
236             CXCursorKind, DeclarationSeq[0]), __traits(getMember, CXCursorKind, DirectiveSeq[0]), __traits(getMember,
237             CXCursorKind, ExpressionSeq[0]), __traits(getMember, CXCursorKind,
238             ExtraSeq[0]), __traits(getMember, CXCursorKind,
239             PreprocessorSeq[0]), __traits(getMember, CXCursorKind, ReferenceSeq[0]), __traits(getMember, CXCursorKind,
240             StatementSeq[0]), __traits(getMember, CXCursorKind, TranslationUnitSeq[0])]) {
241         switch (kind) {
242             mixin(wrapCursor!(test, cursor)(AttributeSeq));
243             mixin(wrapCursor!(test, cursor)(DeclarationSeq));
244             mixin(wrapCursor!(test, cursor)(DirectiveSeq));
245             mixin(wrapCursor!(test, cursor)(ExpressionSeq));
246             mixin(wrapCursor!(test, cursor)(ExtraSeq));
247             mixin(wrapCursor!(test, cursor)(PreprocessorSeq));
248             mixin(wrapCursor!(test, cursor)(ReferenceSeq));
249             mixin(wrapCursor!(test, cursor)(StatementSeq));
250             mixin(wrapCursor!(test, cursor)(TranslationUnitSeq));
251         default:
252             assert(0);
253         }
254     }
255 
256     test.attribute.shouldBeTrue;
257     test.declaration.shouldBeTrue;
258     test.directive.shouldBeTrue;
259     test.expression.shouldBeTrue;
260     test.extra.shouldBeTrue;
261     test.preprocessor.shouldBeTrue;
262     test.reference.shouldBeTrue;
263     test.statement.shouldBeTrue;
264     test.translationunit.shouldBeTrue;
265 }