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