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