1 /** 2 Copyright: Copyright (c) 2017, Joakim Brännström. All rights reserved. 3 License: MPL-2 4 Author: Joakim Brännström (joakim.brannstrom@gmx.com) 5 6 This Source Code Form is subject to the terms of the Mozilla Public License, 7 v.2.0. If a copy of the MPL was not distributed with this file, You can obtain 8 one at http://mozilla.org/MPL/2.0/. 9 */ 10 module dextool.plugin.cpptestdouble.backend.generate_cpp; 11 12 import std.typecons : Yes; 13 14 import cpptooling.data : CppNamespace, LocationTag, CppNs, CppClassName; 15 import cpptooling.data.symbol : Container; 16 17 import dsrcgen.cpp : CppModule, noIndent; 18 19 import dextool.plugin.cpptestdouble.backend.interface_ : Controller, Parameters; 20 import dextool.plugin.cpptestdouble.backend.type : Code, GeneratedData, 21 ImplData, Kind, GtestPrettyPrint; 22 23 /** Translate the structure to code. 24 * 25 * Generates: 26 * - #include's needed by the test double 27 * - recursive starting with the root: 28 * order is important, affects code layout: 29 * - anonymouse instance of the adapter for the test double 30 * - free function implementations using the registered test double 31 * - adapter registering a test double instance 32 */ 33 void generate(ref ImplData impl, Controller ctrl, Parameters params, 34 ref GeneratedData gen_data, ref Container container) { 35 import std.algorithm : filter; 36 import std.path : baseName; 37 import cpptooling.generator.includes : generateIncludes; 38 import cpptooling.generator.func : generateFuncImpl; 39 import cpptooling.generator.gmock : generateGmock; 40 import cpptooling.generator.gtest : generateGtestPrettyPrintHdr, 41 generateGtestPrettyPrintImpl, generateGtestPrettyEqual; 42 43 if (ctrl.doPreIncludes) { 44 gen_data.make(Code.Kind.hdr).include(impl.includeHooks.preInclude.baseName); 45 } 46 47 generateIncludes(params.getIncludes, gen_data.make(Code.Kind.hdr)); 48 49 if (ctrl.doPostIncludes) { 50 gen_data.make(Code.Kind.hdr).include(impl.includeHooks.postInclude.baseName); 51 } 52 53 // dfmt off 54 auto ns_data = GenerateNamespaceData( 55 () { return gen_data.make(Code.Kind.hdr).cpp.base.noIndent; }, 56 () { return gen_data.make(Code.Kind.impl).cpp.base.noIndent; }, 57 (CppNs[] ns, CppClassName name) { return gen_data.makeMock(ns, name).cpp.base.noIndent; }, 58 (CppNs[] ns, CppClassName name) { return gen_data.makeGtestPrettyPrintHdr(ns, name).cpp; }, 59 (CppNs[] ns, CppClassName name) { return gen_data.makeGtestPrettyPrintImpl(ns, name).cpp; }, 60 ); 61 // dfmt on 62 63 foreach (a; impl.root.classRange.filter!(a => impl.lookup(a.id) == Kind.gmock)) { 64 auto mock_ns = ns_data.gmock(a.resideInNs, a.name) 65 .base.namespace(params.getMainNs).noIndent; 66 generateGmock(a, mock_ns, Yes.inlineCtorDtor); 67 } 68 69 foreach (a; impl.root.classRange.filter!(a => impl.lookup(a.id) == Kind.gtestPrettyPrint)) { 70 auto hdr = ns_data.gtestPPHdr(a.resideInNs, a.name); 71 generateGtestPrettyEqual(a.memberPublicRange, a.fullyQualifiedName, 72 cast(string) params.getMainNs, container, hdr); 73 generateGtestPrettyPrintHdr(a.fullyQualifiedName, hdr); 74 generateGtestPrettyPrintImpl(a.memberPublicRange, a.fullyQualifiedName, 75 ns_data.gtestPPImpl(a.resideInNs, a.name)); 76 } 77 78 foreach (a; impl.root.funcRange) { 79 generateFuncImpl(a, ns_data.impl().base); 80 } 81 82 foreach (a; impl.root.namespaceRange()) { 83 generateForEach(impl, a, params, ns_data, container); 84 } 85 } 86 87 private: 88 89 alias LazyModule = CppModule delegate() @safe; 90 alias LazyMockModule = CppModule delegate(CppNs[] ns, CppClassName name) @safe; 91 alias LazyGtestModule = CppModule delegate(CppNs[] ns, CppClassName name) @safe; 92 93 /// Lazily create the modules when they are needed. 94 /// Be wary that each time a LazyModule is called it may generate code. 95 @safe struct GenerateNamespaceData { 96 this(LazyModule hdr, LazyModule impl, LazyMockModule gmock, 97 LazyGtestModule gtestPPHdr, LazyGtestModule gtestPPImpl) { 98 this.hdr = () => hdr().base.noIndent; 99 this.gmock = (CppNs[] ns, CppClassName name) => gmock(ns, name).base.noIndent; 100 101 this.gtestPPHdr = (CppNs[] ns, CppClassName name) => gtestPPHdr(ns, name).base; 102 this.gtestPPImpl = (CppNs[] ns, CppClassName name) => gtestPPImpl(ns, name).base; 103 104 this.impl_ = impl; 105 // never escapes the instances. Assuming it is basically "moved". 106 // TODO: change GenerateNamespaceData to a class? 107 () @trusted { 108 this.impl = () => this.makeImpl().base.noIndent; 109 this.implTop = () => this.makeImplTop().base.noIndent; 110 }(); 111 } 112 113 LazyModule hdr; 114 LazyMockModule gmock; 115 LazyModule impl; 116 LazyModule implTop; 117 118 LazyGtestModule gtestPPHdr; 119 LazyGtestModule gtestPPImpl; 120 121 private: 122 /// Position at the top of the implementation file where e.g. globals go. 123 CppModule makeImplTop() { 124 if (impl_top is null) { 125 auto b = impl_().noIndent; 126 impl_top = b.base.noIndent; 127 impl_mod = b.base.noIndent; 128 } 129 return impl_top; 130 } 131 132 CppModule makeImpl() { 133 makeImplTop; 134 return impl_mod; 135 } 136 137 LazyModule impl_; 138 CppModule impl_top; 139 CppModule impl_mod; 140 } 141 142 /** 143 * TODO code duplication with generate 144 * 145 * recursive to handle nested namespaces. 146 * the singleton ns must be the first code generate or the impl can't use the 147 * instance. 148 */ 149 void generateForEach(ref ImplData impl, ref CppNamespace ns, Parameters params, 150 GenerateNamespaceData gen_data, ref Container container) { 151 import std.algorithm : filter; 152 import cpptooling.data.symbol.types : USRType; 153 import cpptooling.generator.func : generateFuncImpl; 154 import cpptooling.generator.gtest : generateGtestPrettyPrintHdr, 155 generateGtestPrettyPrintImpl, generateGtestPrettyEqual; 156 157 auto ns_data = GenerateNamespaceData(gen_data.hdr, gen_data.impl, 158 gen_data.gmock, gen_data.gtestPPHdr, gen_data.gtestPPImpl); 159 160 switch (impl.lookup(ns.id)) with (Kind) { 161 case none: 162 auto hdrMod() { 163 return gen_data.hdr().namespace(ns.name).noIndent; 164 } 165 166 auto implMod() { 167 return gen_data.impl().namespace(ns.name).noIndent; 168 } 169 170 auto gmockMod(CppNs[] nesting, CppClassName name) { 171 return gen_data.gmock(nesting, name).namespace(ns.name).noIndent; 172 } 173 174 auto gtestModHdr(CppNs[] nesting, CppClassName name) { 175 return gen_data.gtestPPHdr(nesting, name).namespace(ns.name).noIndent; 176 } 177 178 auto gtestModImpl(CppNs[] nesting, CppClassName name) { 179 return gen_data.gtestPPImpl(nesting, name).namespace(ns.name).noIndent; 180 } 181 182 ns_data = GenerateNamespaceData(&hdrMod, &implMod, &gmockMod, 183 >estModHdr, >estModImpl); 184 break; 185 case testDoubleSingleton: 186 import dextool.plugin.backend.cpptestdouble.adapter : generateSingleton; 187 188 // inject the singleton in the previous top position 189 generateSingleton(ns, gen_data.implTop()); 190 break; 191 case testDoubleInterface: 192 break; 193 case testDoubleNamespace: 194 generateNsTestDoubleHdr(ns, params, ns_data.hdr, ns_data.gmock, 195 (USRType usr) => container.find!LocationTag(usr), impl); 196 generateNsTestDoubleImpl(ns, ns_data.impl, impl); 197 break; 198 default: 199 break; 200 } 201 202 foreach (a; ns.funcRange) { 203 generateFuncImpl(a, ns_data.impl().base); 204 } 205 206 foreach (a; ns.classRange.filter!(a => impl.lookup(a.id) == Kind.gtestPrettyPrint)) { 207 auto hdr = ns_data.gtestPPHdr(a.resideInNs, a.name); 208 generateGtestPrettyEqual(a.memberPublicRange, a.fullyQualifiedName, 209 cast(string) params.getMainNs, container, hdr); 210 generateGtestPrettyPrintHdr(a.fullyQualifiedName, hdr); 211 generateGtestPrettyPrintImpl(a.memberPublicRange, a.fullyQualifiedName, 212 ns_data.gtestPPImpl(a.resideInNs, a.name)); 213 } 214 215 foreach (a; ns.namespaceRange) { 216 generateForEach(impl, a, params, ns_data, container); 217 } 218 } 219 220 void generateNsTestDoubleHdr(LookupT)(CppNamespace ns, Parameters params, 221 LazyModule hdr_, LazyMockModule gmock, LookupT lookup, ref ImplData data) { 222 import std.typecons : Yes, No; 223 import cpptooling.generator.classes : generateHdr; 224 import cpptooling.generator.gmock : generateGmock; 225 226 CppModule ns_cache; 227 228 auto cppNs() { 229 if (ns_cache is null) { 230 auto hdr = hdr_(); 231 ns_cache = hdr.namespace(ns.name).noIndent; 232 hdr.sep(2); 233 } 234 return ns_cache.base; 235 } 236 237 foreach (c; ns.classRange()) { 238 switch (data.lookup(c.id)) { 239 case Kind.none: 240 generateHdr(c, cppNs(), No.locationAsComment, lookup, Yes.inlineDtor); 241 break; 242 case Kind.testDoubleInterface: 243 generateHdr(c, cppNs(), 244 No.locationAsComment, lookup, Yes.inlineDtor); 245 break; 246 case Kind.adapter: 247 generateHdr(c, cppNs(), No.locationAsComment, lookup); 248 break; 249 case Kind.gmock: 250 auto mock_ns = gmock(c.resideInNs, c.name) 251 .base.namespace(params.getMainNs).noIndent; 252 generateGmock(c, mock_ns, Yes.inlineCtorDtor); 253 break; 254 default: 255 break; 256 } 257 } 258 } 259 260 void generateNsTestDoubleImpl(CppNamespace ns, LazyModule impl_, ref ImplData data) { 261 import dextool.plugin.backend.cpptestdouble.adapter : generateImpl; 262 263 CppModule cpp_ns; 264 auto cppNs() { 265 if (cpp_ns is null) { 266 auto impl = impl_(); 267 cpp_ns = impl.namespace(ns.name); 268 impl.sep(2); 269 } 270 return cpp_ns; 271 } 272 273 foreach (ref class_; ns.classRange()) { 274 switch (data.lookup(class_.id)) { 275 case Kind.adapter: 276 generateImpl(class_, cppNs()); 277 break; 278 default: 279 break; 280 } 281 } 282 }