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 }