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.backend.cpptestdouble.adapter;
7 
8 import std.typecons : Yes, No;
9 import logger = std.experimental.logger;
10 
11 import dsrcgen.cpp : CppModule;
12 import sumtype;
13 
14 import cpptooling.type : MainNs, MainInterface;
15 
16 // TODO this is a mega include. Reduce it.
17 import cpptooling.data;
18 
19 import cpptooling.data : CppClass, CppNamespace, CppClassName, CppMethodName, USRType;
20 
21 // dfmt off
22 @safe:
23 private struct BuildAdapter {
24     BuildAdapter makeTestDouble(bool value) {
25         this.hasTestDouble = value;
26         return this;
27     }
28 
29     BuildAdapter makeInitGlobals(bool value) {
30         this.hasGlobalInitializer = value;
31         return this;
32     }
33 
34     CppClass finalize() {
35         import cpptooling.data : SimpleFmt, TypeId, PtrFmt;
36 
37         auto c = CppClass(className);
38 
39         CxParam[] params;
40 
41         if (hasTestDouble) {
42             auto attr = TypeAttr.init;
43             attr.isRef = Yes.isRef;
44             auto kind = TypeKind(TypeKind.PointerInfo(PtrFmt(TypeId(interfaceName)),
45                     USRType(interfaceName ~ "&"), [attr]));
46 
47             params ~= makeCxParam(TypeKindVariable(TypeKindAttr(kind,
48                     TypeAttr.init), CppVariable("inst")));
49         }
50 
51         if (hasGlobalInitializer) {
52             auto kind = TypeKind(TypeKind.SimpleInfo(SimpleFmt(TypeId(interfaceInitGlobal))));
53             params ~= makeCxParam(TypeKindVariable(TypeKindAttr(kind,
54                     TypeAttr.init), CppVariable("init_globals")));
55         }
56 
57         c.comment("Adapter connecting an interface with an implementation.");
58         c.comment("The lifetime of the connection is the same as the instance of the adapter.");
59 
60         c.put(CppCtor(makeUniqueUSR, classCtor, params, CppAccess(AccessType.Public)));
61         c.put(CppDtor(makeUniqueUSR, classDtor, CppAccess(AccessType.Public),
62                 CppVirtualMethod(MemberVirtualType.Normal)));
63 
64         return c;
65     }
66 
67 private:
68     CppClassName className;
69     CppClassName interfaceName;
70     CppClassName interfaceInitGlobal;
71     CppMethodName classCtor;
72     CppMethodName classDtor;
73     bool hasTestDouble;
74     bool hasGlobalInitializer;
75 }
76 // dfmt on
77 
78 /// Make a C++ adapter for an interface.
79 BuildAdapter makeAdapter(InterfaceT)(InterfaceT interface_name) {
80     return BuildAdapter(CppClassName("Adapter"), CppClassName(cast(string) interface_name),
81             CppClassName(cast(string) interface_name ~ "_InitGlobals"),
82             CppMethodName("Adapter"), CppMethodName("~Adapter"));
83 }
84 
85 /// make an anonymous namespace containing a ptr to an instance of a test
86 /// double that implement the interface needed.
87 CppNamespace makeSingleton(MainNs main_ns, MainInterface main_if) {
88     import cpptooling.data : CppVariable, CxGlobalVariable, makeUniqueUSR;
89     import cpptooling.data : TypeId, PtrFmt;
90 
91     auto attr = TypeAttr.init;
92     attr.isPtr = Yes.isPtr;
93     auto kind = TypeKind(TypeKind.PointerInfo(PtrFmt(TypeId(main_ns ~ "::" ~ main_if)),
94             USRType(main_ns ~ "::" ~ main_if ~ "*"), [attr]));
95 
96     auto v = CxGlobalVariable(makeUniqueUSR, TypeKindAttr(kind, TypeAttr.init),
97             CppVariable("test_double_inst"));
98     auto ns = CppNamespace.makeAnonymous();
99     ns.put(v);
100 
101     return ns;
102 }
103 
104 /** Generate an adapter implementation.
105  *
106  * The global is expected to be named test_double_inst.
107  */
108 void generateImpl(CppClass c, CppModule impl) {
109     import std.variant : visit;
110     import cpptooling.data;
111     import dsrcgen.c : E;
112 
113     // C'tor is expected to have N params.
114     // One of them must be named inst.
115     static void genCtor(CppClass c, CppCtor m, CppModule impl) {
116         // dfmt off
117         TypeKindVariable p0 = () @trusted {
118             import std.array;
119 
120             return m.paramRange().front.visit!(
121                 (TypeKindVariable tkv) => tkv,
122                 (TypeKindAttr tk) => TypeKindVariable(tk, CppVariable("inst")),
123                 (VariadicType vt) {
124                     logger.error("Variadic c'tor not supported:", m.toString);
125                     return TypeKindVariable(makeSimple("not supported"), CppVariable("not supported"));
126                 })();
127         }();
128         // dfmt on
129 
130         with (impl.ctor_body(m.name.get, E(p0.type.toStringDecl(p0.name)))) {
131             stmt(E("test_double_inst") = E("&" ~ p0.name));
132         }
133         impl.sep(2);
134     }
135 
136     static void genOp(CppClass c, CppMethodOp m, CppModule impl) {
137         // not applicable
138     }
139 
140     static void genDtor(CppClass c, CppDtor m, CppModule impl) {
141         with (impl.dtor_body(c.name)) {
142             stmt("test_double_inst = 0");
143         }
144         impl.sep(2);
145     }
146 
147     static void genMethod(CppClass c, CppMethod m, CppModule impl) {
148         import std.range : takeOne;
149 
150         string params = m.paramRange().joinParams();
151         auto b = impl.method_body(m.returnType.toStringDecl, c.name, m.name,
152                 m.isConst ? Yes.isConst : No.isConst, params);
153         with (b) {
154             auto p = m.paramRange().joinParamNames();
155             stmt(E("test_double_inst") = E("&" ~ p));
156         }
157         impl.sep(2);
158     }
159 
160     foreach (m; c.methodPublicRange()) {
161         // dfmt off
162         () @trusted{
163             m.visit!(
164                 (CppMethod m) => genMethod(c, m, impl),
165                 (CppMethodOp m) => genOp(c, m, impl),
166                 (CppCtor m) => genCtor(c, m, impl),
167                 (CppDtor m) => genDtor(c, m, impl));
168         }();
169         // dfmt on
170     }
171 }
172 
173 /// A singleton to allow the adapter to setup "a" connection.
174 void generateSingleton(CppNamespace in_ns, CppModule impl) {
175     import std.ascii : newline;
176     import cpptooling.data;
177     import dsrcgen.cpp : E;
178 
179     auto ns = impl.namespace("")[$.begin = "{" ~ newline];
180     ns.suppressIndent(1);
181     impl.sep(2);
182 
183     foreach (g; in_ns.globalRange()) {
184         auto stmt = E(g.type.toStringDecl(g.name));
185         g.type.kind.info.match!((TypeKind.PointerInfo t) { stmt = E("0"); }, (_) {
186         });
187         ns.stmt(stmt);
188     }
189 }