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 }