Tracerz
A modern C++ (C++17) procedural generation tool based on @galaxykate's tracery
Install / Use
/learn @caranatar/TracerzREADME
tracerz
A modern C++ (C++17) procedural generation tool based on @galaxykate's tracery language
Table of contents
About
tracerz is a single-header-file procedural generation tool heavily based on @galaxykate's javascript tool tracery. To avoid confusion with an already existing, but abandoned, C++ implementation of tracery, I incremented the last letter instead (:
Dependencies
tracerz uses JSON for Modern C++ for its json handling. It expects the single header file version of that library to be in the include path at "json.hpp"
tracerz's test suite uses Catch2, but this library is only required for development on tracerz itself.
Basic usage
Create a grammar
Place the tracerz.h header file somewhere accessible in your include path, along with the JSON for Modern C++ header, then include it:
#include "tracerz.h"
Create your input grammar (see JSON for Modern C++ for details)
nlohmann::json inGrammar = {
{"rule", "output"}
};
Create the tracerz::Grammar object:
tracerz::Grammar grammar(inGrammar);
To add the base English modifiers supported by tracery, add the following after creating the grammar:
grammar.addModifiers(tracerz::getBaseEngModifiers());
If you need to pop rules off rulestacks (if you do you'll know), it's sufficient for now to know the following:
-
To do so you must import the base extended modifiers:
grammar.addModifiers(tracerz::getBaseExtendedModifiers()); -
In tracerz, popping is done by applying the Tree modifier
pop!!to the rule name:// The following in tracerz: { "#popKey#", "[#key.pop!!#]" } // is equivalent to the following in tracery: { "#popKey#", "[key:POP]" }
To learn more about Tree modifiers like pop!!, see Tree modifiers
Expanding rules
To create a new tree, expand all the nodes, and retrieve the flattened output string, simply call flatten(input) on
the grammar:
grammar.flatten("output is #rule#"); // returns "output is output"
To get a fully expanded tree rooted with the input string, call getExpandedTree(input):
std::shared_ptr<tracerz::Tree> tree = grammar.getExpandedTree("output is #rule#");
Then, to retrieve the output from the tree, simply call flatten on it with the desired modifier function map:
std::string output = tree->flatten(grammar.getModifierFunctions()); // returns "output is output"
Advanced usage
Custom RNG
By default, tracerz uses the C++ standard library's Mersenne Twister algorithm std::mt19937 for random number
generation, seeded with the current time, and std::uniform_int_distribution to provide a uniform distribution across
options when selecting a single expansion from a rule defined as a list. If you want to use a standard library
generator, or any other generator which conforms to the C++ standard's definition of UniformRandomBitGenerator, with
a different seed, you can pass the RNG into the constructor, which will automatically deduce the type:
// Using a standard random number generator
tracerz::Grammar grammar(inGrammar, std::mt19937(seed));
// TestRNG satifies the requirements of UniformRandomBitGenerator
tracerz::Grammar grammar(inGrammar, TestRNG(seed));
To also override the default distribution generator (std::uniform_int_distribution), you must specify its type when
constructing a tracerz::Grammar object:
// Using a custom rng and distribution
tracerz::Grammar<TestRNG, TestDistribution> grammar(inGrammar, TestRNG(seed));
Type requirements
From the viewpoint of tracerz, there is no requirement on the RNG type. If you are using it with the default
distribution, then you must meet the standard's requirements of UniformRandomBitGenerator.
The uniform distribution type has two requirements:
- It must have a constructor taking two parameters a & b, restricting the output of the distribution to the range [a, b].
- It must have an
operator()which can take a parameter of the RNG type and generate a number in the requested range.
Tree modifiers
Tree modifiers are an extended feature of tracerz not present in the original tracery tool. These modifiers take as
input the tree they are working on as a std::shared_ptr<Tree>, as well as the rule name they are being applied to
(i.e., if a tree modifier foo!! is called as #rule.foo!!#, the underlying function will receive the pointer to the
tree as well as the string "rule"). By convention, tracerz uses two exclamation points at the end of tree modifiers to
visually disambiguate them, but this is not enforced in any way by the library.
Tree node modifiers
Tree node modifiers are an extended feature of tracerz not present in the original tracery tool. These modifiers take as
input the tree node they are working on as a std::shared_ptr<TreeNode>, as well as the rule name they are being
applied to (i.e., if a tree node modifier foo! is called as #rule.foo!#, the underlying function will receive the
pointer to the tree node as well as the string "rule"). By convention, tracerz uses one exclamation point at the end of
tree node modifiers to visually disambiguate them, but this is not enforced in any way by the library.
Adding modifiers
Adding output modifiers
Output modifiers act on the output of expanding a rule, they are the only type of modifier present in the original
tracery language. To create a new output modifier, create a std::function that takes at least one std::string as
input, and returns a std::string. To create a modifier that takes parameters when used in the language, simply add
additional std::string parameters to your modifier function.
// Modifier with no parameters
std::function<std::string(const std::string&)> meow = [](const std::string& input) {
return input + " meow!"
};
grammar.addModifier("meow", meow);
// Modifier that takes one parameter
std::function<std::string(const std::string&, const std::string&)> noise = [](const std::string& input,
const std::string& param) {
return input + noise;
};
grammar.addModifier("noise", noise);
In the language, these can be used to the same effect as #rule.meow# and #rule.noise( meow!)#. Note the leading space
in the second example. tracerz maintains whitespace in parameters; only commas separating parameters are removed.
Adding tree modifiers
See Tree modifiers for details on the definition of tree modifiers. To create a tree modifier, create
a std::function that takes a const std::shared_ptr<Tree>&, representing the tree being acted on, and at least one
const std::string&, representing the rule name the modifier is being called on, and returns an empty std::string.
For example:
std::shared_ptr<tracerz::Tree> tree = grammar.getExpandedTree("#rule.foo!!#");
std::function<std::string(const std::shared_ptr<tracerz::Tree>&,const std::string&)> foo = [](const std::shared_ptr<tracerz::Tree>& t, const std::string& r) {
// t == tree
// r == "rule"
return "";
};
grammar.addModifier("foo!!", foo);
Tree modifiers can make any change to the Tree possible through its public API. This includes modifying the structure of
or runtime state of the tree. In tracerz, popping a ruleset off a rulestack is implemented as a Tree modifier (pop!!)
for this reason.
Tree modifiers can also be parameterized in the same way as output modifiers, by adding addition string parameters to the function.
Adding tree node modifiers
See Tree node modifiers for details on the definition of tree node modifiers. To create a tree
node modifier, create a std::function that takes a const std::shared_ptr<TreeNode>&, representing the tree node
being acted on, and at least one const std::string&, representing the rule name the modifier is being called on, and
returns an empty std::string.
Tree node modifiers can make any change to the TreeNode possible through its public API. tracerz does not make use of this functionality at this time, and it is only being provided for completeness.
Tree node modifiers can also be parameterized in the same way as output modifiers, by adding addition string parameters to the function.
Step-by-step tree expansion
To get an unexpanded tree rooted with the input string, call getTree(input):
std::shared_ptr<tracerz::Tree> tree = grammar.getTree("output is #rule#");
To expand the tree one step, call expand on the tree. Note that the Tree object does not track modifiers or the RNG
in use, so these have to be retrieved from the Grammar object in order to perform expansion as expected on the Tree.
In addition, it is necessary to provide template parameters to this method to give the type of the RNG and uniform
distribution:
t
Related Skills
node-connect
343.1kDiagnose OpenClaw node connection and pairing failures for Android, iOS, and macOS companion apps
frontend-design
90.0kCreate distinctive, production-grade frontend interfaces with high design quality. Use this skill when the user asks to build web components, pages, or applications. Generates creative, polished code that avoids generic AI aesthetics.
openai-whisper-api
343.1kTranscribe audio via OpenAI Audio Transcriptions API (Whisper).
qqbot-media
343.1kQQBot 富媒体收发能力。使用 <qqmedia> 标签,系统根据文件扩展名自动识别类型(图片/语音/视频/文件)。
