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