Learning Tracery: Part 2: Grammars (and more grammars!)

Learning Tracery

Tracery is a “story-grammar generation library” written in JavaScript. It was created by Kate Compton and has risen in popularity for its easy-to-follow functions for taking a set of options and creating a grammar that can produce different texts.


As covered in Part 1, a grammar is a set of symbols and some rules to follow. When using Tracery, these are feed to the createGrammar() function to tell it what symbols to use and their possible options.


Screenshot 2018-10-05 16.45.56

In Tracery, symbols and their choices are defined as an object literal. Each property within the object is an array where the property is the symbol name and the array is all of its possible choices.


Screenshot 2018-10-05 16.47.41

These symbols are passed to the createGrammar() function as part of the Tracery object. This takes the properties of the object passed to it to create associations where the property names can be substituted for one of their possible values in the next step.

The createGrammar() function also returns a grammar object that can be feed some text with certain values to substitute for the property names of the object passed to it.


Screenshot 2018-10-05 16.51.52

The flatten() function of the object returned by createGrammar() has a function, flatten(), that “flattens” all of the possible choices for each symbol into one randomly chosen from its choices.

This function returns a text representing any textual substitutions made based on what was passed to it. For any symbols wrapped in hashes (#animals# in this example) it is switched for one of its choices.

Play with the example on Repl.it!

And More Grammars!

Screenshot 2018-10-05 17.01.49

Through adding more symbols, more complicated output can be achieved. Beyond using #animals#, a second symbol, #locations# could be added.

Screenshot 2018-10-05 17.05.53

Adding new symbols with new rules does not change the basic output of the example. However, Tracery also understands symbols within symbols.

Screenshot 2018-10-05 17.08.23

Complex rules can be created that will take symbols and expand them based on other, existing symbols.

Screenshot 2018-10-05 17.11.43

Interaction-based Grammars

Knowing that flatten() will give one of the choices of a symbol can be helpful in using it to set the initial values of an interaction between parties. Through using conditional statements, multiple uses of flatten() can be used to set and test values to create a very basic interaction.

// Create two Tracery objects
var tracery = require("tracery-grammar");
// Create a Person class
function Person(id) {
this.id = id;
this.say = function(words) {
console.log(id + ": " + words);
// Create two simple people objects
var personA = new Person("A");
var personB = new Person("B");
var symbols = {
"like": ["benches", "trees", "grass"]
// Create a grammar
var grammar = tracery.createGrammar(symbols);
// Set what they like
personA.like = grammar.flatten("#like#");
personB.like = grammar.flatten("#like#");
personA.say("I like " + personA.like + ".");
if(personA.like == personB.like) {
personB.say("I also like " + personA.like);
} else {
personB.say("I don't like that. I like " + personB.like + ".");

As a more expanded example, the symbols could be used as keywords and then “flattened” to queue up different responses based on the history of the conversation and the rules established by the grammars. While this example uses the same grammar, different ones could be created and run against each other, prompting repeating usage of flatten() to choose the response based on the symbol each time.

Play with the example on Repl.it!