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