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