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