The parsing side of cparse (Cursor + Parser) turns text into a data structure. The code-generation side (Generator
Indentation) turns a data structure into text. The two are independent — you can use one without the other. A tool that parses a config file and prints a summary uses only the parsing side. A code scaffolder that reads command-line arguments and emits skeleton files uses only the generation side. A grammar-driven code generator like UMTSM uses both, but the two halves never touch each other inside cparse — the coupling happens in the subclass.
This page walks through a small but complete Generator subclass: an emitter that produces a C++ header containing an enum class from a list of enumerator names.
Input and output
Input (given as constructor arguments):
- Header name:
"Colour.hh" - Namespace path:
["graphics", "core"] - Enum type name:
"Colour" - Enumerators:
["red", "green", "blue"]
Desired output:
#ifndef GRAPHICS_CORE_COLOUR_HH
#define GRAPHICS_CORE_COLOUR_HH
namespace graphics
{
namespace core
{
enum class Colour
{
Red,
Green,
Blue
};
} // namespace core
} // namespace graphics
#endif // GRAPHICS_CORE_COLOUR_HH
The interesting details, all handled by Generator helpers:
GRAPHICS_CORE_COLOUR_HHis composed from the namespace path and header name bygenerateHeaderGuard, including the underscore-collapse and leading-underscore strip.Red,Green,Bluecome fromconvertUpperCaseCamelapplied to the lowercase inputs.- The indented braces come from the
Indentationmember being right-shifted around each nested block.
The subclass
#include <cparse/Generator.hh>
#include <cparse/Indentation.hh>
#include <fstream>
#include <string>
#include <vector>
class EnumHeaderGen : public fedem::parser::Generator
{
public:
EnumHeaderGen(std::string const& path,
std::vector<std::string> const& namespacePath,
std::string enumName,
std::vector<std::string> enumerators)
: out_(path)
, path_(namespacePath)
, name_(std::move(enumName))
, enumerators_(std::move(enumerators))
, header_(fs::path(path).filename().string())
{}
// Required overrides
std::ostream& getStream() override { return out_; }
fedem::parser::Indentation& getIndentor() override { return indent_; }
void generate() override
{
emitHeaderGuardOpen();
emitBlankLine();
emitNamespaceScopes(0);
emitBlankLine();
emitHeaderGuardClose();
}
// Namespace-stack pure virtuals
void openNamespaceScope() noexcept override {
auto const& name = namespaceNameContainer.back();
getStream() << getIndentor() << "namespace " << name << '\n'
<< getIndentor() << "{\n";
getIndentor().right();
}
void closeNamespaceScope() noexcept override {
auto const name = namespaceNameContainer.back();
getIndentor().left();
getStream() << getIndentor() << "} // namespace " << name << '\n';
namespaceNameContainer.pop_back();
}
std::string getNamespacePath() const noexcept override {
std::string path;
for (auto const& n : namespaceNameContainer) {
if (!path.empty()) path += "::";
path += n;
}
return path;
}
private:
void emitHeaderGuardOpen() {
auto const guard = generateHeaderGuard(header_);
getStream() << "#ifndef " << guard << '\n'
<< "#define " << guard << '\n';
}
void emitHeaderGuardClose() {
getStream() << "#endif // " << generateHeaderGuard(header_) << '\n';
}
void emitBlankLine() { getStream() << '\n'; }
// Recursive descent through the namespace path, then the enum.
void emitNamespaceScopes(size_t depth) {
if (depth == path_.size()) {
emitEnum();
return;
}
namespaceNameContainer.push_back(path_[depth]);
openNamespaceScope();
emitNamespaceScopes(depth + 1);
closeNamespaceScope();
}
void emitEnum() {
getStream() << getIndentor() << "enum class " << name_ << '\n'
<< getIndentor() << "{\n";
getIndentor().right();
for (size_t i = 0; i < enumerators_.size(); ++i) {
getStream() << getIndentor()
<< convertUpperCaseCamel(enumerators_[i])
<< (i + 1 < enumerators_.size() ? ",\n" : "\n");
}
getIndentor().left();
getStream() << getIndentor() << "};\n";
}
std::ofstream out_;
fedem::parser::Indentation indent_; // default step size = 2
std::vector<std::string> path_;
std::string name_;
std::vector<std::string> enumerators_;
std::string header_;
};
Reading the subclass
Two data members meet the two required accessors. getStream() returns out_, an std::ofstream opened by the constructor. getIndentor() returns indent_, an Indentation with the default step size of 2. Neither Generator nor Indentation owns these — they are yours; the base class just holds pointers.
Three namespace pure virtuals give you total control over emission format. In our C++ target, openNamespaceScope() writes namespace X\n{ and right-shifts the indent; closeNamespaceScope() left-shifts and writes } // namespace X; getNamespacePath() joins with ::. A different target — say, generating Rust modules — would emit mod X { / } and join with :: or / as needed.
generate() composes the emission from small helpers. No templating engine, no string interpolation library. Each helper either calls getStream() << getIndentor() << … (an indented line) or manipulates the indent counter. The pattern reads the way the emitted code looks.
Case conversion happens at the emission point. The enumerator names arrive lowercase ("red", "green", "blue") and are converted with convertUpperCaseCamel immediately before writing them. The data model stays canonical; the presentation choice lives at the emission site.
The header guard is composed once from the namespace path plus the header name. generateHeaderGuard("Colour.hh") — when the namespace stack is ["graphics", "core"] and thus getNamespacePath() returns "graphics::core" — produces "GRAPHICS_CORE_COLOUR_HH". The dot in .hh becomes _; consecutive underscores collapse; leading underscores strip. All of it is documented in Generator::generateHeaderGuard Notes.
Using it
int main()
{
EnumHeaderGen gen(
"Colour.hh",
{ "graphics", "core" },
"Colour",
{ "red", "green", "blue" }
);
gen.generate();
// Colour.hh now contains the header shown at the top of this page.
}
Extending
The pattern above scales in a few natural directions:
Multiple emit passes. If you emit both a .hh and a .cpp for the same input, either instantiate two subclasses (each with its own stream) or open the second stream inside generate(). Separate instances is cleaner because getStream() cannot sensibly return two different streams.
Reading input from a parser. Replace the constructor's raw-argument list with parser output: run a Parser subclass over an input file, extract the parsed data into your subclass's members, then call generate(). UMTSM is the canonical example — parses a state machine description with cparse, emits C++ state-machine code with Generator/Indentation. The two halves never call each other directly; the coupling is entirely in the subclass.
Encoding strings safely. If your emitted code embeds user-supplied strings as C++ string literals — for tag values, error messages, resource names — use Generator::encodeCppString to escape them:
getStream() << '"' << encodeCppString(userValue) << '"';
The static method handles the standard C escape sequences and octal-encodes non-printable bytes. It is safer than assembling the string literal by hand.
Alternative naming conventions. Override generateFilename if you want snake_case.hh files instead of UpperCamel.hh, or generateHeaderGuard if you want a different guard scheme (a UUID prefix, a project tag).
What Generator does not provide
Repeating the class synopsis's Notes for emphasis — worth knowing before you reach for the wrong tool:
- No file management. Your subclass opens the stream.
- No error handling.
generate()returns void; if you need a diagnostic collector, add one yourself. - No template engine. Generator emits byte by byte. If you want substitution templates, use a real templating library and let Generator handle just the case conversion and header-guard bits.
- No language awareness. The three namespace pure virtuals are virtual precisely because different targets need different emission syntax.
See also
- Reference › Generator — the class synopsis with the full member table.
- Reference › Indentation — the small value type driving the indent counter.
- Grammar Patterns — the parsing-side companion to this page.

