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 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(CppClass adapter, 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.get)) {
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.get, 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(CppClass adapter, CppMethodOp m, CppModule impl) {
226         // not applicable
227     }
228 
229     void genDtor(CppClass adapter, CppDtor m, CppModule impl) {
230         AdapterKind kind;
231         if (auto l = lookup(m.usr.get)) {
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(CppClass adapter, 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.match!(
273                 (CppMethod m) => genMethod(adapter, m, impl),
274                 (CppMethodOp m) => genOp(adapter, m, impl),
275                 (CppCtor m) => genCtor(adapter, m, impl),
276                 (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 std.algorithm : map;
286     import cpptooling.data;
287     import dsrcgen.cpp : E;
288 
289     auto ns = impl.namespace("")[$.begin = "{" ~ newline];
290     ns.suppressIndent(1);
291     impl.sep(2);
292 
293     foreach (g; in_ns.globalRange) {
294         auto stmt = E(g.type.toStringDecl(g.name));
295         g.type.kind.info.match!((const TypeKind.PointerInfo t) { stmt = E("0"); }, (_) {
296         });
297         ns.stmt(stmt);
298     }
299 }