1 /**
2 Date: 2015-2017, 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 dextool.plugin.ctestdouble.backend.adapter;
7 
8 import logger = std.experimental.logger;
9 
10 import dsrcgen.cpp : CppModule;
11 
12 import dextool.type : StubPrefix;
13 import cpptooling.data : CppClass, CppClassName, CppNamespace, CppNs;
14 import dextool.plugin.ctestdouble.backend.global : MutableGlobal;
15 
16 @safe:
17 
18 /// The kind of constructors the adapters ctor can be. Affects the generated
19 /// code.
20 enum AdapterKind {
21     none,
22     ctor_initGlobal,
23     ctor_testDouble,
24     ctor_testDouble_zeroGlobal,
25     ctor_testDouble_initGlobal,
26     dtor_empty,
27     dtor_testDouble,
28 }
29 
30 private struct BuildAdapter {
31     import cpptooling.data : CppClass, CppClassName, CppMethodName;
32 
33     private {
34         CppClassName className;
35         CppClassName interfaceName;
36         CppClassName interfaceInitGlobal;
37         CppMethodName classCtor;
38         CppMethodName classDtor;
39         bool hasTestDouble;
40         bool hasGlobalInitializer;
41         bool zeroGlobalsInitializer;
42     }
43 
44     BuildAdapter makeTestDouble(bool value) {
45         this.hasTestDouble = value;
46         return this;
47     }
48 
49     BuildAdapter makeInitGlobals(bool value) {
50         this.hasGlobalInitializer = value;
51         return this;
52     }
53 
54     BuildAdapter makeZeroGlobals(bool value) {
55         this.zeroGlobalsInitializer = value;
56         return this;
57     }
58 
59     /** Finalize the construction of a class representation the adapter.
60      *
61      * Additional metadata about the kinds the adapters c'tor and d'tor is.
62      * Used for code generation.
63      */
64     CppClass finalize(ImplDataT)(ref ImplDataT data) {
65         import std.typecons : Yes;
66         import cpptooling.data : AccessType, CxParam, CppCtor, CppDtor,
67             CppAccess, CppVirtualMethod, CppVariable, makeUniqueUSR,
68             makeCxParam, MemberVirtualType, TypeAttr, TypeKind, TypeKindAttr,
69             TypeKindVariable, TypeId, PtrFmt;
70 
71         auto c = CppClass(className);
72         c.comment("Adapter connecting an interface with an implementation.");
73         c.comment("The lifetime of the connection is the same as the instance of the adapter.");
74 
75         CxParam[] params;
76 
77         if (hasTestDouble) {
78             auto attr = TypeAttr.init;
79             attr.isRef = Yes.isRef;
80             auto kind = TypeKind(TypeKind.PointerInfo(PtrFmt(TypeId(interfaceName)),
81                     makeUniqueUSR, [attr]));
82 
83             params ~= makeCxParam(TypeKindVariable(TypeKindAttr(kind,
84                     TypeAttr.init), CppVariable("inst")));
85         }
86 
87         if (hasGlobalInitializer) {
88             auto attr = TypeAttr.init;
89             attr.isRef = Yes.isRef;
90             auto kind = TypeKind(TypeKind.PointerInfo(PtrFmt(TypeId(interfaceInitGlobal)),
91                     makeUniqueUSR, [attr]));
92             params ~= makeCxParam(TypeKindVariable(TypeKindAttr(kind,
93                     TypeAttr.init), CppVariable("init_globals")));
94         }
95 
96         if (params.length != 0) {
97             auto ctor = CppCtor(makeUniqueUSR, classCtor, params[0 .. 1],
98                     CppAccess(AccessType.Public));
99             c.put(ctor);
100 
101             // the priority order is important.
102             // 1. for the c'tor with one parameter generate a default
103             //    initialization to zero if the user hasn't specified
104             //     --no-zeroglobals.
105             // 2. it is then possible that there exist globals and free
106             //    functions.  Because of the previous choice by the user it is
107             //    preferred to generate a test double as the one-parameter
108             //    c'tor before the global variable initializer.
109             if (hasTestDouble && hasGlobalInitializer && zeroGlobalsInitializer) {
110                 data.adapterKind[ctor.usr] = AdapterKind.ctor_testDouble_zeroGlobal;
111             } else if (hasTestDouble) {
112                 data.adapterKind[ctor.usr] = AdapterKind.ctor_testDouble;
113             } else if (hasGlobalInitializer) {
114                 data.adapterKind[ctor.usr] = AdapterKind.ctor_initGlobal;
115             }
116         }
117         if (params.length == 2) {
118             auto ctor = CppCtor(makeUniqueUSR, classCtor, params, CppAccess(AccessType.Public));
119             c.put(ctor);
120             data.adapterKind[ctor.usr] = AdapterKind.ctor_testDouble_initGlobal;
121         }
122 
123         auto dtor = CppDtor(makeUniqueUSR, classDtor, CppAccess(AccessType.Public),
124                 CppVirtualMethod(MemberVirtualType.Normal));
125         c.put(dtor);
126         if (hasTestDouble) {
127             data.adapterKind[dtor.usr] = AdapterKind.dtor_testDouble;
128         } else {
129             data.adapterKind[dtor.usr] = AdapterKind.dtor_empty;
130         }
131 
132         return c;
133     }
134 }
135 
136 /// Make a C++ adapter for an interface.
137 auto makeAdapter(InterfaceT)(InterfaceT interface_name) {
138     import cpptooling.data : CppMethodName;
139 
140     return BuildAdapter(CppClassName("Adapter"), CppClassName(cast(string) interface_name),
141             CppClassName(cast(string) interface_name ~ "_InitGlobals"),
142             CppMethodName("Adapter"), CppMethodName("~Adapter"));
143 }
144 
145 /// make an anonymous namespace containing a ptr to an instance of a test
146 /// double that implement the interface needed.
147 CppNamespace makeSingleton(CppNs namespace_name, CppClassName type_name, string instance_name) {
148     import std.typecons : Yes;
149     import cpptooling.data : CppVariable, CxGlobalVariable, makeUniqueUSR,
150         TypeAttr, TypeKind, USRType, TypeKindAttr, TypeId, PtrFmt;
151 
152     auto attr = TypeAttr.init;
153     attr.isPtr = Yes.isPtr;
154     auto kind = TypeKind(TypeKind.PointerInfo(PtrFmt(TypeId(namespace_name ~ "::" ~ type_name)),
155             USRType(namespace_name ~ "::" ~ type_name ~ "*"), [attr]));
156 
157     auto v = CxGlobalVariable(makeUniqueUSR, TypeKindAttr(kind, TypeAttr.init),
158             CppVariable(instance_name));
159     auto ns = CppNamespace.makeAnonymous();
160     ns.put(v);
161 
162     return ns;
163 }
164 
165 /** Generate an adapter implementation.
166  *
167  * The global is expected to be named test_double_inst.
168  */
169 void generateImpl(LookupKindT)(CppClass adapter, MutableGlobal[] globals,
170         StubPrefix prefix, CppModule impl, LookupKindT lookup) {
171     import std.variant : visit;
172     import cpptooling.data : CppVariable, CppCtor, CppMethodOp, CppDtor,
173         CppMethod, joinParams, joinParamNames;
174     import dsrcgen.c : E;
175 
176     static void genCallsToGlobalInitializer(MutableGlobal[] globals,
177             CppVariable instance, CppModule impl) {
178         import cpptooling.data : methodNameToString;
179 
180         foreach (global; globals) {
181             impl.stmt(E(instance).e(global.name)(""));
182         }
183     }
184 
185     void genCtor(const ref CppClass adapter, const ref CppCtor m, CppModule impl) {
186         import cpptooling.data : paramNameToString;
187         import cpptooling.data : TypeKind;
188 
189         AdapterKind kind;
190         if (auto l = lookup(m.usr)) {
191             kind = *l;
192         } else {
193             logger.error("c'tor for the adapter is corrupted");
194             return;
195         }
196 
197         string params = m.paramRange().joinParams();
198         auto body_ = impl.ctor_body(m.name, params);
199 
200         switch (kind) with (AdapterKind) {
201         case ctor_initGlobal:
202             genCallsToGlobalInitializer(globals,
203                     CppVariable(m.paramRange[0].paramNameToString), body_);
204             break;
205         case ctor_testDouble:
206             body_.stmt(E("test_double_inst") = E("&" ~ m.paramRange[0].paramNameToString));
207             break;
208         case ctor_testDouble_zeroGlobal:
209             body_.stmt(E("test_double_inst") = E("&" ~ m.paramRange[0].paramNameToString));
210             body_.stmt(prefix ~ "ZeroGlobals" ~ " init_globals");
211             genCallsToGlobalInitializer(globals, CppVariable("init_globals"), body_);
212             break;
213         case ctor_testDouble_initGlobal:
214             body_.stmt(E("test_double_inst") = E("&" ~ m.paramRange[0].paramNameToString));
215             genCallsToGlobalInitializer(globals,
216                     CppVariable(m.paramRange[1].paramNameToString), body_);
217             break;
218         default:
219             assert(0);
220         }
221 
222         impl.sep(2);
223     }
224 
225     static void genOp(const ref CppClass adapter, const ref CppMethodOp m, CppModule impl) {
226         // not applicable
227     }
228 
229     void genDtor(const ref CppClass adapter, const ref CppDtor m, CppModule impl) {
230         AdapterKind kind;
231         if (auto l = lookup(m.usr)) {
232             kind = *l;
233         } else {
234             logger.error("d'tor for the adapter is corrupted");
235             return;
236         }
237 
238         switch (kind) with (AdapterKind) {
239         case dtor_empty:
240             impl.dtor_body(adapter.name);
241             break;
242         case dtor_testDouble:
243             with (impl.dtor_body(adapter.name)) {
244                 stmt("test_double_inst = 0");
245             }
246             break;
247         default:
248             assert(0);
249         }
250 
251         impl.sep(2);
252     }
253 
254     static void genMethod(const ref CppClass adapter, const ref CppMethod m, CppModule impl) {
255         import std.range : takeOne;
256         import std.typecons : Yes, No;
257         import cpptooling.data : toStringDecl;
258 
259         string params = m.paramRange().joinParams();
260         auto b = impl.method_body(m.returnType.toStringDecl, adapter.name,
261                 m.name, m.isConst ? Yes.isConst : No.isConst, params);
262         with (b) {
263             auto p = m.paramRange().joinParamNames();
264             stmt(E("test_double_inst") = E("&" ~ p));
265         }
266         impl.sep(2);
267     }
268 
269     foreach (m; adapter.methodPublicRange()) {
270         // dfmt off
271         () @trusted{
272             m.visit!(
273                 (const CppMethod m) => genMethod(adapter, m, impl),
274                 (const CppMethodOp m) => genOp(adapter, m, impl),
275                 (const CppCtor m) => genCtor(adapter, m, impl),
276                 (const CppDtor m) => genDtor(adapter, m, impl));
277         }();
278         // dfmt on
279     }
280 }
281 
282 /// A singleton to allow the adapter to setup "a" connection.
283 void generateSingleton(CppNamespace in_ns, CppModule impl) {
284     import std.ascii : newline;
285     import cpptooling.data;
286     import dsrcgen.cpp : E;
287 
288     auto ns = impl.namespace("")[$.begin = "{" ~ newline];
289     ns.suppressIndent(1);
290     impl.sep(2);
291 
292     foreach (g; in_ns.globalRange()) {
293         auto stmt = E(g.type.toStringDecl(g.name));
294         if (g.type.kind.info.kind == TypeKind.Info.Kind.pointer) {
295             stmt = E("0");
296         }
297         ns.stmt(stmt);
298     }
299 }