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.
What it produces
Section titled “What it produces”Given (write-by-hand form, before the first run):
#pragma once
namespace app{
enum class Color { Red, Green, Blue };
// [[codegen::generated::ToString::app::Color]]
} // namespace appThe 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:
codegen -i include/color.hpp -r .codegen/rules -a ToStringThe header becomes:
#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 appThe 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:
// 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 rule scripts
Section titled “The rule scripts”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, andnode.enumerators[]. It builds onecaseper enumerator, wraps it in a switch, and returns{ source = impl, inline = { { source = decl } } }. isScopedcontrols whether case values use theEnumName::prefix. Scoped enums (enum class) need it; unscoped enums don’t.- No grouping script. Each enum’s output goes to a
.g.cppnext 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.
Extending this rule
Section titled “Extending this rule”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.
- 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.isScopeddistinguishesenum classfrom plainenumfor the case-value prefix.- No grouping script ⇒ output goes next to the input header (
color.hpp→color.g.cpp). - The first run rewrites bare anchors into paired
:begin]] / :end]]blocks; later runs are idempotent.