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                 &gtestModHdr, &gtestModImpl);
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 }