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 }