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 this.impl = () => this.makeImpl().base.noIndent; 105 this.implTop = () => this.makeImplTop().base.noIndent; 106 } 107 108 LazyModule hdr; 109 LazyMockModule gmock; 110 LazyModule impl; 111 LazyModule implTop; 112 113 LazyGtestModule gtestPPHdr; 114 LazyGtestModule gtestPPImpl; 115 116 private: 117 /// Position at the top of the implementation file where e.g. globals go. 118 CppModule makeImplTop() { 119 if (impl_top is null) { 120 auto b = impl_().noIndent; 121 impl_top = b.base.noIndent; 122 impl_mod = b.base.noIndent; 123 } 124 return impl_top; 125 } 126 127 CppModule makeImpl() { 128 makeImplTop; 129 return impl_mod; 130 } 131 132 LazyModule impl_; 133 CppModule impl_top; 134 CppModule impl_mod; 135 } 136 137 /** 138 * TODO code duplication with generate 139 * 140 * recursive to handle nested namespaces. 141 * the singleton ns must be the first code generate or the impl can't use the 142 * instance. 143 */ 144 void generateForEach(ref ImplData impl, ref CppNamespace ns, Parameters params, 145 GenerateNamespaceData gen_data, ref const Container container) { 146 import std.algorithm : filter; 147 import cpptooling.data.symbol.types : USRType; 148 import cpptooling.generator.func : generateFuncImpl; 149 import cpptooling.generator.gtest : generateGtestPrettyPrintHdr, 150 generateGtestPrettyPrintImpl, generateGtestPrettyEqual; 151 152 auto ns_data = GenerateNamespaceData(gen_data.hdr, gen_data.impl, 153 gen_data.gmock, gen_data.gtestPPHdr, gen_data.gtestPPImpl); 154 155 switch (impl.lookup(ns.id)) with (Kind) { 156 case none: 157 auto hdrMod() { 158 return gen_data.hdr().namespace(ns.name).noIndent; 159 } 160 161 auto implMod() { 162 return gen_data.impl().namespace(ns.name).noIndent; 163 } 164 165 auto gmockMod(const CppNs[] nesting, const CppClassName name) { 166 return gen_data.gmock(nesting, name).namespace(ns.name).noIndent; 167 } 168 169 auto gtestModHdr(const CppNs[] nesting, const CppClassName name) { 170 return gen_data.gtestPPHdr(nesting, name).namespace(ns.name).noIndent; 171 } 172 173 auto gtestModImpl(const CppNs[] nesting, const CppClassName name) { 174 return gen_data.gtestPPImpl(nesting, name).namespace(ns.name).noIndent; 175 } 176 177 ns_data = GenerateNamespaceData(&hdrMod, &implMod, &gmockMod, 178 >estModHdr, >estModImpl); 179 break; 180 case testDoubleSingleton: 181 import dextool.plugin.backend.cpptestdouble.adapter : generateSingleton; 182 183 // inject the singleton in the previous top position 184 generateSingleton(ns, gen_data.implTop()); 185 break; 186 case testDoubleInterface: 187 break; 188 case testDoubleNamespace: 189 generateNsTestDoubleHdr(ns, params, ns_data.hdr, ns_data.gmock, 190 (USRType usr) => container.find!LocationTag(usr), impl); 191 generateNsTestDoubleImpl(ns, ns_data.impl, impl); 192 break; 193 default: 194 break; 195 } 196 197 foreach (a; ns.funcRange) { 198 generateFuncImpl(a, ns_data.impl().base); 199 } 200 201 foreach (a; ns.classRange.filter!(a => impl.lookup(a.id) == Kind.gtestPrettyPrint)) { 202 auto hdr = ns_data.gtestPPHdr(a.resideInNs, a.name); 203 generateGtestPrettyEqual(a.memberPublicRange, a.fullyQualifiedName, 204 cast(string) params.getMainNs, container, hdr); 205 generateGtestPrettyPrintHdr(a.fullyQualifiedName, hdr); 206 generateGtestPrettyPrintImpl(a.memberPublicRange, a.fullyQualifiedName, 207 ns_data.gtestPPImpl(a.resideInNs, a.name)); 208 } 209 210 foreach (a; ns.namespaceRange) { 211 generateForEach(impl, a, params, ns_data, container); 212 } 213 } 214 215 void generateNsTestDoubleHdr(LookupT)(CppNamespace ns, Parameters params, 216 LazyModule hdr_, LazyMockModule gmock, LookupT lookup, ref ImplData data) { 217 import std.typecons : Yes, No; 218 import cpptooling.generator.classes : generateHdr; 219 import cpptooling.generator.gmock : generateGmock; 220 221 CppModule ns_cache; 222 223 auto cppNs() { 224 if (ns_cache is null) { 225 auto hdr = hdr_(); 226 ns_cache = hdr.namespace(ns.name).noIndent; 227 hdr.sep(2); 228 } 229 return ns_cache.base; 230 } 231 232 foreach (c; ns.classRange()) { 233 switch (data.lookup(c.id)) { 234 case Kind.none: 235 generateHdr(c, cppNs(), No.locationAsComment, lookup, Yes.inlineDtor); 236 break; 237 case Kind.testDoubleInterface: 238 generateHdr(c, cppNs(), 239 No.locationAsComment, lookup, Yes.inlineDtor); 240 break; 241 case Kind.adapter: 242 generateHdr(c, cppNs(), No.locationAsComment, lookup); 243 break; 244 case Kind.gmock: 245 auto mock_ns = gmock(c.resideInNs, c.name) 246 .base.namespace(params.getMainNs).noIndent; 247 generateGmock(c, mock_ns); 248 break; 249 default: 250 break; 251 } 252 } 253 } 254 255 void generateNsTestDoubleImpl(CppNamespace ns, LazyModule impl_, ref ImplData data) { 256 import dextool.plugin.backend.cpptestdouble.adapter : generateImpl; 257 258 CppModule cpp_ns; 259 auto cppNs() { 260 if (cpp_ns is null) { 261 auto impl = impl_(); 262 cpp_ns = impl.namespace(ns.name); 263 impl.sep(2); 264 } 265 return cpp_ns; 266 } 267 268 foreach (ref class_; ns.classRange()) { 269 switch (data.lookup(class_.id)) { 270 case Kind.adapter: 271 generateImpl(class_, cppNs()); 272 break; 273 default: 274 break; 275 } 276 } 277 }