Skip to content

ToString: enum ⇒ switch

The ToString example is the canonical demonstration of anchor-based triggering and inline injection. It generates a std::string_view toString(EnumType e) function for any enum you annotate, placing the implementation in a .g.cpp next to the input header and injecting the declaration back into the header.

Given (write-by-hand form, before the first run):

include/color.hpp (before generation)
#pragma once
namespace app
{
enum class Color { Red, Green, Blue };
// [[codegen::generated::ToString::app::Color]]
} // namespace app

The third anchor segment is the qualified name of the target entity — namespace path included. Above, app::Color because the enum lives in namespace app. There is no qualified modifier; the path is just the qualified name.

After running:

Terminal window
codegen -i include/color.hpp -r .codegen/rules -a ToString

The header becomes:

include/color.hpp (after generation)
#pragma once
namespace app
{
enum class Color { Red, Green, Blue };
// [[codegen::generated::ToString::app::Color:begin]]
std::string_view toString(app::Color e);
// [[codegen::generated::ToString::app::Color:end]]
} // namespace app

The bare anchor was expanded into the paired :begin]] / :end]] form. Subsequent runs replace only the content between the markers.

A sibling .g.cpp is produced next to the input header:

include/color.g.cpp
// Generated with Codegen - DO NOT EDIT!!!
#include <string_view>
#include "color.hpp"
std::string_view toString(app::Color e)
{
switch (e)
{
case app::Color::Red: return "Red";
case app::Color::Green: return "Green";
case app::Color::Blue: return "Blue";
default: return "<unknown>";
}
}

The // Generated with Codegen - DO NOT EDIT!!! banner, the #include "color.hpp" self-include, and any project includes are emitted by the engine; the rule’s preamble (#include <string_view>) is added on top.

The shipping example lives at clis/codegen/examples/ToString/. Key behaviours:

  • Preamble emits #include <string_view>.
  • Handler decodes the entity, reads node.identifier.name, node._namespaces, node.isScoped, and node.enumerators[]. It builds one case per enumerator, wraps it in a switch, and returns { source = impl, inline = { { source = decl } } }.
  • isScoped controls whether case values use the EnumName:: prefix. Scoped enums (enum class) need it; unscoped enums don’t.
  • No grouping script. Each enum’s output goes to a .g.cpp next to its input header.

The example’s enum is anchor-triggered (no [[codegen::ToString]] attribute on the enum itself), because C++ does not allow attributes on enum declarations the same way as on structs. The anchor doubles as the inline-injection site for the declaration.

Note: No grouping script. Grouping is optional and omitting it is safe; each entity routes to its default 1:1 path derived from the input header.

Add a reverse fromString. Return a second item in the inline list at a separate anchor (one anchor per <rule>::<qualifiedName>), or extend the handler so its source emits both toString and fromString functions.

Handle explicit values. node.enumerators[].value carries explicit values as source-text strings (null when unset). Use it to detect sparse enums.

Namespace-bucketed output. Add a grouping script that routes by entity.namespaces[1] to e.g. generated/<top-namespace>/strings.g.cpp.

Key Takeaways
  • Anchor comments (// [[codegen::generated::Rule::Qualified]]) trigger the rule and mark the inline-injection site.
  • The third anchor segment is the qualified name; app::Color, Outer::Inner::E, etc.
  • node.isScoped distinguishes enum class from plain enum for the case-value prefix.
  • No grouping script ⇒ output goes next to the input header (color.hppcolor.g.cpp).
  • The first run rewrites bare anchors into paired :begin]] / :end]] blocks; later runs are idempotent.