Skip to content

Commit 3d38f59

Browse files
committed
perf(Builder): render templates directly to ostream
1 parent a89e82f commit 3d38f59

File tree

6 files changed

+176
-167
lines changed

6 files changed

+176
-167
lines changed

src/lib/Gen/hbs/Builder.cpp

+21-44
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,9 @@
1010
//
1111

1212
#include "Builder.hpp"
13-
#include "lib/Support/Radix.hpp"
1413
#include <lib/Lib/ConfigImpl.hpp>
1514
#include <mrdocs/Metadata/DomCorpus.hpp>
1615
#include <mrdocs/Support/Path.hpp>
17-
#include <llvm/Support/FileSystem.h>
1816
#include <llvm/Support/Path.h>
1917
#include <fmt/format.h>
2018
#include <filesystem>
@@ -149,23 +147,25 @@ Builder(
149147

150148
//------------------------------------------------
151149

152-
Expected<std::string>
150+
Expected<void>
153151
Builder::
154152
callTemplate(
153+
std::ostream& os,
155154
std::string_view name,
156155
dom::Value const& context)
157156
{
158157
auto pathName = files::appendPath(layoutDir(), name);
159158
MRDOCS_TRY(auto fileText, files::getFileText(pathName));
160159
HandlebarsOptions options;
161160
options.escapeFunction = escapeFn_;
162-
Expected<std::string, HandlebarsError> exp =
163-
hbs_.try_render(fileText, context, options);
161+
OutputRef out(os);
162+
Expected<void, HandlebarsError> exp =
163+
hbs_.try_render_to(out, fileText, context, options);
164164
if (!exp)
165165
{
166166
return Unexpected(Error(exp.error().what()));
167167
}
168-
return *exp;
168+
return {};
169169
}
170170

171171
//------------------------------------------------
@@ -215,56 +215,35 @@ createContext(
215215
}
216216

217217
template<class T>
218-
Expected<std::string>
218+
requires std::derived_from<T, Info> || std::same_as<T, OverloadSet>
219+
Expected<void>
219220
Builder::
220-
operator()(T const& I)
221+
operator()(std::ostream& os, T const& I)
221222
{
222-
auto const templateFile = fmt::format("index.{}.hbs", domCorpus.fileExtension);
223+
std::string const templateFile =
224+
std::derived_from<T, Info> ?
225+
fmt::format("index.{}.hbs", domCorpus.fileExtension) :
226+
fmt::format("index-overload-set.{}.hbs", domCorpus.fileExtension);
223227
dom::Object ctx = createContext(I);
224228

225229
auto& config = domCorpus->config;
226230
bool isSinglePage = !config->multipage;
227231
if (config->embedded ||
228232
isSinglePage)
229233
{
230-
return callTemplate(templateFile, ctx);
234+
return callTemplate(os, templateFile, ctx);
231235
}
232236

233237
auto const wrapperFile = fmt::format("wrapper.{}.hbs", domCorpus.fileExtension);
234238
dom::Object wrapperCtx = createFrame(ctx);
235-
wrapperCtx.set("contents", dom::makeInvocable([this, &I, templateFile](
239+
wrapperCtx.set("contents", dom::makeInvocable([this, &I, templateFile, &os](
236240
dom::Value const& options) -> Expected<dom::Value>
237241
{
238242
// Helper to write contents directly to stream
239-
return callTemplate(templateFile, createContext(I));
240-
}));
241-
return callTemplate(wrapperFile, wrapperCtx);
242-
}
243-
244-
Expected<std::string>
245-
Builder::
246-
operator()(OverloadSet const& OS)
247-
{
248-
auto const templateFile = fmt::format("index-overload-set.{}.hbs", domCorpus.fileExtension);
249-
dom::Object ctx = createContext(OS);
250-
251-
auto& config = domCorpus->config;
252-
bool isSinglePage = !config->multipage;
253-
if (config->embedded ||
254-
isSinglePage)
255-
{
256-
return callTemplate(templateFile, ctx);
257-
}
258-
259-
auto const wrapperFile = fmt::format("wrapper.{}.hbs", domCorpus.fileExtension);
260-
dom::Object wrapperCtx = createFrame(ctx);
261-
wrapperCtx.set("contents", dom::makeInvocable([this, &OS, templateFile](
262-
dom::Value const& options) -> Expected<dom::Value>
263-
{
264-
// Helper to write contents directly to stream
265-
return callTemplate(templateFile, createContext(OS));
243+
MRDOCS_TRY(callTemplate(os, templateFile, createContext(I)));
244+
return {};
266245
}));
267-
return callTemplate(wrapperFile, wrapperCtx);
246+
return callTemplate(os, wrapperFile, wrapperCtx);
268247
}
269248

270249
Expected<void>
@@ -352,14 +331,12 @@ commonTemplatesDir(std::string_view subdir) const
352331
subdir);
353332
}
354333

355-
356334
// Define Builder::operator() for each Info type
357-
#define DEFINE(T) template Expected<std::string> \
358-
Builder::operator()<T>(T const&)
359-
360-
#define INFO(Type) DEFINE(Type##Info);
335+
#define INFO(T) template Expected<void> Builder::operator()<T##Info>(std::ostream&, T##Info const&);
361336
#include <mrdocs/Metadata/InfoNodesPascal.inc>
362337

338+
template Expected<void> Builder::operator()<OverloadSet>(std::ostream&, OverloadSet const&);
339+
363340
} // hbs
364341
} // mrdocs
365342
} // clang

src/lib/Gen/hbs/Builder.hpp

+5-7
Original file line numberDiff line numberDiff line change
@@ -57,12 +57,9 @@ class Builder
5757
with the index template as the contents.
5858
*/
5959
template<class T>
60-
Expected<std::string>
61-
operator()(T const&);
62-
63-
/// @copydoc operator()(T const&)
64-
Expected<std::string>
65-
operator()(OverloadSet const&);
60+
requires std::derived_from<T, Info> || std::same_as<T, OverloadSet>
61+
Expected<void>
62+
operator()(std::ostream& os, T const&);
6663

6764
/** Render the contents in the wrapper layout.
6865
@@ -123,8 +120,9 @@ class Builder
123120

124121
/** Render a Handlebars template from the templates directory.
125122
*/
126-
Expected<std::string>
123+
Expected<void>
127124
callTemplate(
125+
std::ostream& os,
128126
std::string_view name,
129127
dom::Value const& context);
130128

src/lib/Gen/hbs/MultiPageVisitor.cpp

+68-55
Original file line numberDiff line numberDiff line change
@@ -17,77 +17,90 @@ namespace clang {
1717
namespace mrdocs {
1818
namespace hbs {
1919

20+
template <class T>
21+
requires std::derived_from<T, Info> || std::same_as<T, OverloadSet>
2022
void
2123
MultiPageVisitor::
22-
writePage(
23-
std::string_view text,
24-
std::string_view filename)
24+
operator()(T const& I0)
2525
{
26-
std::string path = files::appendPath(outputPath_, filename);
27-
std::string dir = files::getParentDir(path);
28-
auto exp = files::createDirectory(dir);
29-
if (!exp)
30-
{
31-
exp.error().Throw();
32-
}
33-
std::ofstream os;
34-
try
26+
// If T is an OverloadSet, we make a copy for the lambda because
27+
// these are temporary objects that don't live in the corpus.
28+
// Otherwise, the lambda will capture a reference to the corpus Info.
29+
auto Ref = [&I0] {
30+
if constexpr (std::derived_from<T, Info>)
31+
{
32+
return std::ref(I0);
33+
}
34+
else if constexpr (std::same_as<T, OverloadSet>)
35+
{
36+
return OverloadSet(I0);
37+
}
38+
}();
39+
ex_.async([this, Ref](Builder& builder)
3540
{
36-
os.open(path,
37-
std::ios_base::binary |
38-
std::ios_base::out |
39-
std::ios_base::trunc // | std::ios_base::noreplace
41+
T const& I = Ref;
42+
43+
// ===================================
44+
// Open the output file
45+
// ===================================
46+
std::string path = files::appendPath(outputPath_, builder.domCorpus.getXref(I));
47+
std::string dir = files::getParentDir(path);
48+
if (auto exp = files::createDirectory(dir); !exp)
49+
{
50+
exp.error().Throw();
51+
}
52+
std::ofstream os;
53+
try
54+
{
55+
os.open(path,
56+
std::ios_base::binary |
57+
std::ios_base::out |
58+
std::ios_base::trunc // | std::ios_base::noreplace
4059
);
41-
os.write(text.data(), static_cast<std::streamsize>(text.size()));
42-
}
43-
catch(std::exception const& ex)
44-
{
45-
formatError(R"(std::ofstream("{}") threw "{}")", path, ex.what()).Throw();
46-
}
47-
}
60+
if (!os.is_open()) {
61+
formatError(R"(std::ofstream("{}") failed)", path)
62+
.Throw();
63+
}
64+
}
65+
catch (std::exception const& ex)
66+
{
67+
formatError(R"(std::ofstream("{}") threw "{}")", path, ex.what())
68+
.Throw();
69+
}
4870

49-
template<std::derived_from<Info> T>
50-
void
51-
MultiPageVisitor::
52-
operator()(T const& I)
53-
{
54-
ex_.async([this, &I](Builder& builder)
55-
{
56-
if(const auto r = builder(I))
57-
writePage(*r, builder.domCorpus.getXref(I));
58-
else
59-
r.error().Throw();
60-
if constexpr(
71+
// ===================================
72+
// Generate the output
73+
// ===================================
74+
if (auto exp = builder(os, I); !exp)
75+
{
76+
exp.error().Throw();
77+
}
78+
79+
// ===================================
80+
// Traverse the symbol members
81+
// ===================================
82+
if constexpr (std::derived_from<T, Info>)
83+
{
84+
if constexpr(
6185
T::isNamespace() ||
6286
T::isRecord() ||
6387
T::isEnum())
88+
{
89+
corpus_.traverseOverloads(I, *this);
90+
}
91+
}
92+
else if constexpr (std::same_as<T, OverloadSet>)
6493
{
65-
// corpus_.traverse(I, *this);
66-
corpus_.traverseOverloads(I, *this);
94+
corpus_.traverse(I, *this);
6795
}
6896
});
6997
}
7098

71-
void
72-
MultiPageVisitor::
73-
operator()(OverloadSet const& OS)
74-
{
75-
ex_.async([this, OS](Builder& builder)
76-
{
77-
if(const auto r = builder(OS))
78-
writePage(*r, builder.domCorpus.getXref(OS));
79-
else
80-
r.error().Throw();
81-
corpus_.traverse(OS, *this);
82-
});
83-
}
84-
85-
#define DEFINE(T) template void \
86-
MultiPageVisitor::operator()<T>(T const&)
87-
88-
#define INFO(Type) DEFINE(Type##Info);
99+
#define INFO(T) template void MultiPageVisitor::operator()<T##Info>(T##Info const&);
89100
#include <mrdocs/Metadata/InfoNodesPascal.inc>
90101

102+
template void MultiPageVisitor::operator()<OverloadSet>(OverloadSet const&);
103+
91104
} // hbs
92105
} // mrdocs
93106
} // clang

src/lib/Gen/hbs/MultiPageVisitor.hpp

+2-14
Original file line numberDiff line numberDiff line change
@@ -32,11 +32,6 @@ class MultiPageVisitor
3232
std::string_view outputPath_;
3333
Corpus const& corpus_;
3434

35-
void
36-
writePage(
37-
std::string_view text,
38-
std::string_view filename);
39-
4035
public:
4136
MultiPageVisitor(
4237
ExecutorGroup<Builder>& ex,
@@ -54,16 +49,9 @@ class MultiPageVisitor
5449
respective tasks are also pushed to the executor group.
5550
5651
*/
57-
template <std::derived_from<Info> T>
52+
template <class T>
53+
requires std::derived_from<T, Info> || std::same_as<T, OverloadSet>
5854
void operator()(T const& I);
59-
60-
/** Push a task for the specified OverloadSet to the executor group.
61-
62-
If the OverloadSet object refers to other Info objects, their
63-
respective tasks are also pushed to the executor group.
64-
65-
*/
66-
void operator()(OverloadSet const& OS);
6755
};
6856

6957
} // hbs

0 commit comments

Comments
 (0)