bracery
v1.1.2
Published
Procedural text generator, somewhat compatible with Tracery
Downloads
32
Readme
Bracery
Bracery (bracery.org) is a small language for procedural text generation. Its purpose is to enable quick and fluid writing of text with random elements, allowing for user extensions that may include calls to a server (e.g. for synonyms, rhymes, or other functions).
Bracery aims...
- to keep out of the writer's way, looking mostly like a markup language and close to plain text;
- to be easy to work with, presenting simply whether you're a casual user or an experienced programmer;
- to avoid plundering the keyboard for syntax, especially common punctuation, like quotes;
- to allow real programming, with variables, functions, and lists, but without forcing the writer to learn (or care) about all that;
- to be usable offline by default, but also readily connectable to online generative text servers;
- to be secure running random code from the internet, including limits on recursion and network/CPU usage;
- to be compatible as much as possible with previous work, especially @galaxykate's Tracery.
Bracery combines a few different tricks well-known to computational linguistics and adjacent fields, such as variable manipulation syntax from Tracery, alternations from regular expressions, natural language processing from the compromise library (and, optionally, rhymes and phonemes from RiTa), parsing algorithms from bioinformatics, and lists from Scheme.
Procedural text repository
A writable repository of Bracery scripts is available at bracery.org. Edit Bracery in the web browser; save and share immediately; deploy as a Twitter bot with a single click.
Bracery can also be run from the command line, in the browser, etc.
The Bracery command-line client will use the bracery.org server to resolve symbol definitions if the -w
switch is specified from the command line, e.g.
bin/bracery -w -e '~common_animal'
will generate a sample from this page.
You can also type bin/bracery -wr
to enter text interactively from the command-line
and have expansions returned by the server
(or, alternatively, just point your web browser at bracery.org, click "Edit", and type into the box).
The micro-wiki repository is serverless, built using AWS Lambda functions available in the lambda/ directory.
Programmer's tl;dr
If you're a programmer, or not, you may want the summary:
Bracery is a text markup language
oriented around probabilistic context-free grammars
that uses dollars signs $
for variables (as in $x
),
equals for assignment ($x=value
),
curly-brace delimiters ($x={longer value}
),
square-bracket alternations ([first option|another option|third option]
),
ampersands for built-in function names (&add
, &subtract
, &plural
, etc.),
conditionals (&if
),
lists (&map
, &join
, &reduce
),
access to several parsers (&eval
, "e
, &syntax
, &parse
),
and other language-oriented features;
with an engine that runs asynchronously and can (optionally) connect to an online wiki for procedural content,
where each page is its own symbol in the grammar.
Usage
The following Bracery code generates lines like
how goes it, magician of Middle Earth
and well met, magus of the world
[hello|well met|how goes it|greetings], [wizard|witch|mage|magus|magician|sorcerer|enchanter] of [earthsea|Earth|Middle Earth|the planet|the world]
Here's the same example, but using variables to keep track of the choices:
$greetings=[hello|well met|how goes it|greetings]
$wizard=[wizard|witch|mage|magus|magician|sorcerer|enchanter]
$earthsea=[earthsea|Earth|Middle Earth|the planet|the world]
$greetings, $wizard of $earthsea
You can also use variables to store Bracery code itself, for later expansion:
$greetings="e{[hello|well met|how goes it|greetings]}
$wizard="e{[wizard|witch|mage|magus|magician|sorcerer|enchanter]}
$earthsea="e{[earthsea|Earth|Middle Earth|the planet|the world]}
&eval{$greetings}, &eval{$wizard} of &eval{$earthsea}
The above example uses dynamic evaluation. Here's the same code with some syntactic sugar for the way variables are assigned and expanded:
[greetings=>hello|well met|how goes it|greetings]
[wizard=>wizard|witch|mage|magus|magician|sorcerer|enchanter]
[earthsea=>earthsea|Earth|Middle Earth|the planet|the world]
#greetings#, #wizard# of #earthsea#
Programmers may recognize this kind of thing too (lambdas):
$greetings=[hello|well met|how goes it|greetings]
$wizard=[wizard|witch|mage|magus|magician|sorcerer|enchanter]
$earthsea=[earthsea|Earth|Middle Earth|the planet|the world]
$sentence=&function{$name}{$greetings, $name}
&$sentence{$wizard of $earthsea}
And maybe this as well (lists):
$greetings=[hello|well met|how goes it|greetings]
$wizard=[wizard|witch|mage|magus|magician|sorcerer|enchanter]
$earthsea=[earthsea|Earth|Middle Earth|the planet|the world]
$sentence={$greetings, $wizard of $earthsea}
&join{&shuffle{&split{$sentence}}}
which gives jumbled-up output like
witch the hello, of world
There is a built-in (limited) rhyming engine, using RiTa or the CMU Pronouncing Dictionary for text-to-phoneme conversion. If you supply two grammar templates, Bracery will make a (limited) effort to find two expansions that rhyme:
&rhyme{The [stupid|foolish|hungry] [cat|dog], }{the [lonely|fateful] [hat|log]}
Note that the rhyming algorithm is essentially "brute-force": Bracery generates a few different possibilities, and preferentially selects the ones that rhyme. So, this could take a while if the rhyme templates are complex, and it won't always work (and it will tend to work best if you choose templates that you know will contain some rhymes, as in this example).
Bracery's alternations form a context-free grammar, and Bracery includes a limited parser. In other words, if you have some text that you think might have been generated by a particular Bracery program, then you can reconstruct how that program could have generated that output. This is like running the program backwards! And it only works if the program is very simple (e.g. no functions can be used, nor can variables be modified while the program is running).
Here's an example, using the syntactically ambiguous phrase "fruit flies like a banana":
[sentence=>[#singular_noun# #singular_verb#|#plural_noun# #plural_verb#] #noun_phrase#]
[noun_phrase=>#noun#|#preposition# #noun#]
[noun=>#plural_noun#|#singular_noun#]
[singular_noun=>fruit|a banana]
[singular_verb=>flies|likes|nears]
[plural_noun=>fruit flies|bananas]
[plural_verb=>fly|like|near]
[preposition=>like|near]
&json&parse#sentence#{fruit flies like a banana}
This should output one of two different parses of the phrase. One parse has "fruit flies" as the noun, and "like" as the verb:
[["root",["#sentence#",["alt",["#plural_noun#",["alt","fruit flies"]]," ",["#plural_verb#",["alt","like"]]," ",["#prep_or_noun#",["alt",["#noun#",["alt",["#singular_noun#",["alt","a banana"]]]]]]]]]]
The other parse has "fruit" as the noun, "flies" as the verb, and "like" as a preposition:
[["root",["#sentence#",["alt",["#singular_noun#",["alt","fruit"]]," ",["#singular_verb#",["alt","flies"]]," ",["#prep_or_noun#",["alt",["#prep#",["alt","like"]]," ",["#noun#",["alt",["#singular_noun#",["alt","a banana"]]]]]]]]]]
Bracery's &parse
function is stochastic: if multiple valid parses exist, it will return a random parse,
sampled proportionally to the probability that it's the correct parse.
Finally, note that you don't need to use any of these programmer-oriented features, if you just want to write generative text. Just start typing and go!
Web usage
The "wizard of earthsea" example is available as a web demo (source).
Here's another example, taken from Kate Compton's online tutorial to Tracery:
[name=>Arjun|Yuuma|Darcy|Mia|Chiaki|Izzi|Azra|Lina]
[animal=>unicorn|raven|sparrow|scorpion|coyote|eagle|owl|lizard|zebra|duck|kitten]
[mood=>vexed|indignant|impassioned|wistful|astute|courteous]
[story=>#hero# traveled with her pet #heroPet#. #hero# was never #mood#, for the #heroPet# was always too #mood#.]
[origin=>#[hero:#name#][heroPet:#animal#]story#]
#origin#
This example generates lines like the following:
Darcy traveled with her pet kitten. Darcy was never wistful, for the kitten was always too astute.
Alternate formats
There are several other ways you can specify the definitions. For example, you can use Tracery-style JSON:
{
"name": ["Arjun","Yuuma","Darcy","Mia","Chiaki","Izzi","Azra","Lina"],
"animal": ["unicorn","raven","sparrow","scorpion","coyote","eagle","owl","lizard","zebra","duck","kitten"],
"mood": ["vexed","indignant","impassioned","wistful","astute","courteous"],
"story": ["#hero# traveled with her pet #heroPet#. #hero# was never #mood#, for the #heroPet# was always too #mood#."],
"origin": ["#[hero:#name#][heroPet:#animal#]story#"]
}
Here is a web demo (source) using the Tracery-style JSON symbol definitions. These can also be found, along with other examples from Kate's online tutorial, in the examples directory of this repository.
Command-line usage
Give Bracery some text to expand:
bracery -e '[hello|hi] [world|planet]!'
Or specify a definitions file and play with command-line options. For example, starting with a Tracery-format file, travel.json, which you can grab as follows
curl -O https://raw.githubusercontent.com/ihh/bracery/master/examples/travel.json
Then do a few things with it
bracery -d travel.json
bracery -d travel.json -n5
bracery -d travel.json -n5 --eval '#origin# And then they met #name#.'
bracery -d travel.json -n5 --eval '#origin# And they had [fun|trouble|no luck], until they met #name#.'
bracery -d travel.json --tree
bracery -d travel.json --repl
bracery -d travel.json -n5 --async
These examples all load the Tracery travel.json
rules as user extensions, using the -d
option.
If you specify the -b
option, the command-line tool will convert and output the Tracery JSON to Bracery code.
You can run the tool in client/server mode (NB this is a very light implementation, mostly just a toy example to demonstrate networked symbol expansion):
bracery -d travel.json -S 8000 &
bracery -C http://localhost:8000/ -e '#origin#'
You can also connect directly to the repository at https://bracery.org/
bin/bracery -w -e '~common_animal'
bin/bracery -w -e '&xget~common_animal'
To get a list of available options
bracery --help
From NodeJS
Same example from @galaxykate's online tutorial
var bracery = require('bracery')
var b = new bracery.Bracery
({"name": ["Arjun","Yuuma","Darcy","Mia","Chiaki","Izzi","Azra","Lina"],
"animal": ["unicorn","raven","sparrow","scorpion","coyote","eagle","owl","lizard","zebra","duck","kitten"],
"mood": ["vexed","indignant","impassioned","wistful","astute","courteous"],
"story": ["#hero# traveled with her pet #heroPet#. #hero# was never #mood#, for the #heroPet# was always too #mood#."],
"origin": ["#[hero:#name#][heroPet:#animal#]story#"]})
console.log (b.expand().text)
You should see an output like
Lina traveled with her pet owl. Lina was never wistful, for the owl was always too courteous.
The expand()
method tries to guess the starting nonterminal, but you can override this, or add other stuff like variable bindings, e.g.
console.log (b.expand('#origin#',{vars:{name:'Berenice'}}))
console.log (b.expand('#origin# And then they met #name#.'))
and so on.
Dynamic bindings
When using the node API, tilde-prefixed symbols like ~name
can be bound to JavaScript functions:
var bracery = require('../bracery')
var b = new bracery.Bracery
({"percentage": function (config) { return Math.round (config.random() * 100) + ' percent' }})
console.log (b.expand('I [love|hate|like] you ~percentage!').text)
If a callback
is specified,
the functions can return promises:
var bracery = require('../bracery')
var b = new bracery.Bracery ({ percentage: function (config) {
return new Promise (function (resolve, reject) {
setTimeout (function() {
resolve (Math.round (config.random() * 100) + ' percent')
}, 1000)
})
}})
console.log ('Calculating...')
b.expand ('I [love|hate|like] you ~percentage!',
{ callback: function (expansion) { console.log (expansion.text) } })
Technical details
Namespaces
Bracery defines three separate namespaces, distinguished by the prefix character.
You can ignore these and just use the Tracery syntax #name#
if you want, but for a deeper understanding of what's going on:
$name
refers to a variable&name
refers to a core library function or macro~name
refers to a user extension (local or remote)#name#
means "expand variable$name
if defined, otherwise call user extension~name
Limits on program complexity
Bracery was designed to be run on unfiltered user input. Since it is capable of general programming, it must also include configurable constraints on the amount of resources a program is allowed to consume, otherwise a user program could easily send it into an infinite loop or otherwise hog CPU. Another reason to impose limits is that recursion in Bracery is implemented using recursion in JavaScript, with no tail call optimization, so heavily recursive Bracery code can quickly max out the JavaScript stack.
The main constraints that Bracery enforces are maximum parse tree depth, parse tree node count, recursion depth, and output length.
For the &parse
function, constraints on the parsed sequence and subsequence lengths are also enforced.
Comparison with Markdown and Twine
The syntax &link{hint}{destination}
will be rendered in an implementation-determined way
(the default implementation just passes it through unchanged);
the same is true of &reveal{hint}{revelation}
, except that revelation
is expanded whereas destination
is quoted.
At bracery.org, both are modeled as anchor elements:
destination
is Bracery source text that replaces the current Bracery source text when the hint
is clicked,
so that an interactive fiction-style pattern is &link{Doorway description}{~next_room}
where the tilde-prefixed symbol ~next_room
plays a similar role to a Twine passage;
whereas revelation
is pre-expanded text that is revealed when the hint
is clicked (replacing the clicked element).
The Markdown-inspired syntax [Next room.]{You enter the next room... #next_room#}
is a shortcut for &link{Next room.}{You enter the next room... #next_room#}
.
(Note that Bracery is orthogonal to most of Markdown; a Markdown postprocessing step is a common addition.)
The Twine-inspired syntax [[Next room.]]
is a shortcut for &link{Next room.}{#next_room#}
.
(The symbol name is derived from the link text by lower-casing and replacing some interior characters with underscores.)
Comparison with Tracery
In Tracery, variables and symbols share the same namespace, as part of the design.
For example, #sentence#
is the syntax to expand the nonterminal symbol sentence
,
and it is also the syntax for retrieving and expanding the value of the variable named sentence
.
If the variable has been specified in the local context of the running program (i.e. the text up to that point),
then that specified value overrides the original nonterminal symbol definition (if there was one).
Bracery keeps faith with this aspect of Tracery's design, expanding #sentence#
the same way as Tracery does, with locally-specified variables overriding globally-specified symbol definitions.
However, Bracery also has syntax allowing programmers to access the local variable's value directly (as $sentence
) or expand the original global nonterminal (as ~sentence
).
It also introduces dynamic evaluation and conditional primitives, which are required to connect the above elements (#sentence#
, ~sentence
and $sentence
),
but are also quite powerful in their own right.
Distinction between symbols and variables
As well as the flanking hash-character notation that Tracery uses for symbol expansions from the grammar, #symbol#
,
Bracery allows the tilde character prefix, ~symbol
.
The Bracery variant carries the additional, specific nuance that you want to use the original symbol definitions file (or other authority) to expand the symbol,
as opposed to any subsequently defined variables.
Thus, if b
is the Bracery object with the example grammar defined in the NodeJS section above,
then b.expand('[name:PERRY] #name# ').text
will always give the result PERRY
,
but b.expand('[name:PERRY] ~name ').text
will give Arjun
, or Yuuma
, or Darcy
and so on,
according to the example grammar.
If you just want the variable value, you can use the dollar character prefix, $name
, which will evaluate to the empty string if the variable has not been defined.
So, b.expand('[name:PERRY] $name ').text
. will always be PERRY
, again,
but b.expand('$name').text
will be the empty string.
Regex-like shorthands for procedural grammars
Bracery also allows other ways of generating repetitive, regex-like grammars, such as alternations
console.log (b.expand ('[hello|hallo|hullo]').text)
which should give hello
, hallo
or hullo
, and repetitions
console.log (b.expand ('&rep{hello }{3,5}').text)
which should yield from three to five hello
's, with a space after each.
See tests for more examples using the JavaScript API.
Built-in functions
Bracery also offers a number of built-in functions for processing text (e.g. case, tense, plurals) and lists. These are described under Syntax.
Rationale
Bracery works just fine as a synchronous library, running from a local symbol definitions file, like Tracery (this is the default when running from the command-line, or using the node API). However, Bracery was specifically designed to work well for asynchronous applications where the client is decoupled from the symbol definition store.
In asynchronous mode, the symbol expansion code can run on a server somewhere remote from the client (i.e. the place where procedural text generation is happening, such as the user's web browser). This means that, for example, the set of definitions can potentially be very big (including a "standard library"), or can be continually updated, or collaboratively edited.
In order to allow programmers to write efficient code in this framework, Bracery's syntax distinguishes between expansions that can be performed on the client, from those that must be performed by the server. The former (client expansions) are called variables and the latter (server expansions) are called symbols.
Syntax
The formal grammar for Bracery is in src/rhs.peg.js (specified using PEG.js)
Language features include
- named nonterminals:
- Tracery-style
#symbol_name#
to expand a variable, falling back to an external definition (i.e. user extension)
- Bracery-style
&$symbol_name
or&${symbol_name}
to expand a variable~symbol_name
or~{symbol_name}
to expand an externally-defined symbol (user extension)
- Tracery-style
- alternations (anonymous nonterminals):
[option1|option 2|Option number three|Some other option...]
- can be nested:
[option1|option 2|3rd opt|4th|more [options|nested options]...]
- variables:
- Tracery-style
[variable_name:value]
to assign[variable_name=>value]
to quote-assign (this is a Bracery-specific extension)
#variable_name#
to retrieve and expand, defaulting to externally-defined symbol~name
- all names are case-insensitive
- Bracery-style
$variable_name={value}
to assign- braces can be omitted if
value
has no whitespace or punctuation
- braces can be omitted if
$variable_name
or${variable_name}
to retrieve (without expanding)&eval{$variable_name}
or&$variable_name
to retrieve and expand
- the Tracery-style syntax
#name#
is equivalent to&$name
if variable$name
is defined, otherwise falls back to calling user extension~name
- Tracery-style
- built-in text-processing functions:
&plural{...}
(plural),&a{...}
("a" or "an")&cap{...}
(Capitalize),&lc{...}
and&uc{...}
(lower- & UPPER-case)- selected natural language-processing functions from compromise including
- (for nouns)
&singular
and&topic
- (for verbs)
&past
,&present
,&future
,&infinitive
,&adjective
,&negative
- (for nouns)
- natural language-friendly arithmetic using compromise:
&add{2}{4}
gives6
&add{two}{4}
givessix
,&add{two cats}{4}
givessix cats
- form of result is determined by first argument, so
&add{4}{two}
and&add{4}{two cats}
both evaluate to6
- form of result is determined by first argument, so
&subtract{x}{y}
behaves like&add
&multiply{x}{y}
,÷{x}{y}
,&pow{x}{y}
return digits only:&multiply{ten cats}{two dogs}
is20
&math{($x+$y*$z)/$a}
defines a context that allows infix arithmetic operators&ordinal{3}
is3rd
,&cardinal{3rd}
is3
&dignum{3}
is3
,&wordnum{three}
isthree
&random{n}
,&floor{x}
,&ceil{x}
,&round{x}
do what you probably expect&prob{p}{succeed}{fail}
expands tosucceed
with probabilityp
, andfail
otherwise
&eq{x}{y}
,&neq{x}{y}
,>{x}{y}
,&geq{x}{y}
,<{x}{y}
,&leq{x}{y}
also fairly predictable- similarly
&inc{$x}
,&dec{$x}
,$x++
,++$x
,$x--
,--$y
- and
$x+=1
,$x-=2
,$x*=3
,$x/=4
- and
- regular expressions:
&match/regex/flags{text}{expr}
returns a list ofexpr
evaluations ($$1
,$$2
, etc are bound to matching groups)&replace/regex/flags{text}{replacement}
returns a string&split/regex/flags{text}
or just&split{text}
returns a list
- special functions:
- repetition:
&rep{x}{3}
expands toxxx
&rep{x}{3,5}
expands toxxx
,xxxx
, orxxxxx
- conditionals:
&if{testExpr}then{trueExpr}else{falseExpr}
- Evaluates to
trueExpr
iftestExpr
contains any non-whitespace characters, andfalseExpr
otherwise - The
falseExpr
clause is optional and defaults to the empty string - The
then
andelse
keywords are optional; you can write&if{testExpr}{trueExpr}{falseExpr}
or&if{testExpr}{trueExpr}
- The conditional test (
testExpr
) can use arithmetic operators&eq
,&neq
,>
,<
,&geq
,&leq
- also comparison
&same{x}{y}
and boolean operators&and{x}{y}
,&or{x}{y}
,¬{x}
- also comparison
- dynamic evaluation
&eval{expr}
parsesexpr
as Bracery and dynamically expands it- conversely,
"e{expr}
returnsexpr
as a text string, without doing any expansions "e{...}
,&unquote{...}
,&strictquote{...}
work pretty much like quasiquote/unquote/quote in Scheme&\
{...},
&,{...},
&'{...}` are the corresponding shorthand equivalents
- conversely,
&eval{"e{expr}}
is the same asexpr
, although...- there is a configurable limit on the number of dynamic evaluations that an expression can use, to guard against infinite recursion or hammering the server
"ify{expr}
wraps a string or (nested) list with"e
and&list
(shorthand is&q
)
- locally scoped variables:
- Tracery-style
#[x:value1][y:value2]symbol_name#
(what Tracery calls "actions") - Bracery-style
&let$x={value1}$y={value2}{something involving x and y}
- Tracery-style
- first-class functions (or, at the very least, frequent-flyer functions that got an upgrade)
&call{expr}{arg1}{arg2}{arg3...}
binds$$1
toarg1
,$$2
toarg2
,$$3
toarg3
... before expandingexpr
- in other words,
&let$$1={arg1}{&let$$2={arg2}{&let$$3={arg3}{...}}}
&$x
is short for&call{$x}
- in other words,
&apply{expr}{args}
is the same but the arguments are in list form&function$arg1$arg2$arg3{...}
is exactly the same as"e{&let$arg1={$$1}{&let$arg2={$$2}{&let$arg3={$$3}{...}}}}
- you can also pass args to user extensions e.g.
&~extension{arg1}{arg2}{arg3}
&~extension
is short for&xcall{~extension}
- the 'apply' form of this is
&xapply{~extension}{arglist}
- the implementation may optionally allow retrieval of the Bracery code behind an extension symbol, using the syntax
&xget{~extension}
, but this is not guaranteed- specifically, extensions don't have to be implemented in Bracery themselves
- for those that are, however, it's useful to be able to retrieve the code in order to do syntactic analysis of the underlying context-free grammar
- the
&parse
function uses this feature
- repetition:
- lists:
&list{...}
or just&{...}
creates an explicit nested list context, vs the default string context- e.g.
$a=&list{"e{1}"e{2}"e{3}}
creates a list, as does$b=&list{1&,2&,3}
or$c=&{1&,2&,3}
or$d=&makelist{1}{2}{3}
&{}
is the empty list, equivalent to&list{}
- beginning a string context with
{}
(or any other list) makes it a list context - beginning a string context with a string, or wrapping it in
"e{...}
makes it a string context
- e.g.
&islist{x}
returns true if, and only if,x
is a list- list-coercing functions:
&prepend{item}{list}
,&append{list}{item}
return lists&first{list}
,&last{list}
return individual list items (can be strings or nested lists)¬first{list}
,¬last{list}
return lists&nth{index}{list}
returns item numberindex
(0-based) fromlist
&indexof{item}{list}
returns index ofitem
inlist
, or the (falsy) empty string ifitem
is not inlist
&cat{list1}{list2}
returns a list&join{list}{item}
returns a string&map$varname:{list}{expr}
and&filter$varname:{list}{expr}
return lists&reduce$varname:{list}$result={init}{expr}
can return list or string&for:$varname{list}{expr}
returns the empty list, discarding the expansions ofexpr
(but keeping the side-effects)&shuffle{list}
returns a shuffled list&rotate{list}
returns a rotated list (first element moved to back)&bump{list}
returns a pseudo-rotated list (first element moved to random place in back half)
&makelist{a}{b}{c}{d}{etc}
returns a list and is equivalent to&list{&value{a}&value{b}&value{c}&value{d}&value{etc}}
"elist{$a}{$b}{$c}{$d}{etc}
returns a list and is equivalent to&list{"e{$a}"e{$b}"e{$c}"e{$d}"e{etc}}
&numsort$varname{list}{weightExpr}
and&lexsort$varname{list}{tagExpr}
return lists, numerically- or lexically-sorted (respectively) by the corresponding mapped expression
- when coerced into a list context by one of the above functions, the empty string becomes the empty list and any nonempty string becomes a single-element list
- when coerced into a string context (i.e. most contexts), a list is invisibly joined/flattened as if by
&join{list}{}
- The special constructs
&cycle
and&queue
are useful for deterministically (vs stochastically) iterating through a range of options each time the code is called:&cycle$x{list}
sets$x
tolist
the first time it's called, then rotates$x
thereafter&queue$x{list}
sets$x
tolist
the first time it's called, then shifts elements off$x
thereafter- unlike
&cycle
,&queue
does not repeat; when all elements have been shifted off,$x
is the empty list, which is a non-truthy value - the return value of both
&cycle$x{list}
and&queue$x{list}
is not the new value of$x
, but rather&eval{&first{$x}}
;$x
itself represents the series of options to be iterated through, and you generally only want to display the first of these at any given time - these constructs are useful with
&makelist
and"elist
, e.g.[tick=>You feel &cycle$mood&makelist{happy}{sad}{bored}.] #tick# #tick# #tick# #tick#
expands toYou feel happy. You feel sad. You feel bored. You feel happy.
- Similarly,
&playlist$x{list}
cycles throughlist
in a non-repeating random order, using&shuffle
and&bump
- functions, alternations, repetitions, variable assignments, and conditionals can be arbitrarily nested
- everything can occur asynchronously, so symbols can be resolved and expanded from a remote store
- but if you have a synchronously resolvable store (i.e. a local Tracery object), everything can work synchronously too
- access to the parser (disabled by default, for performance guarantees; to enable, set
{ enableParse: true }
in the configuration object for theexpand
method)&syntax{...}
returns the parse tree for the given Bracery expression&parse{source}{expr}
returns a parse tree by which Bracery expressionsource
might have generatedexpr
, or the empty string if no parse exists- The parse is probabilistic, not deterministic: if multiple valid parses exist, then the parse tree is sampled from the posterior probability distribution of valid parse trees
- The source expression
source
(and any other Bracery code that it indirectly invokes) may not contain any function calls or variable assignments, only symbol references and variable lookups/expansions. This makes it a strict context-free grammar - The parser is not guaranteed to work correctly if the grammar contains null cycles (i.e. a series of transformations that leads back to the original symbol, with no other sequence generated) -
&grammar{source}
returns the (almost) Chomsky normal-form grammar used by&parse
, obtained by syntactic analysis of thesource
expression . An example of such a null cycle is[a=>#b#|x] [b=>#a#]
- The parser uses a variant of the Inside algorithm to sample from the posterior distribution of valid parses
- The full Inside algorithm takes time O(L^3) and memory O(L^2) where L is the string length. This is rather expensive for long strings, so Bracery's implementation restricts the maximum subclause length to be K (via the
maxSubsequenceLength
config parameter), leading to memory O(KL) and time O(LK^2) - NB the
&syntax
function uses Parsing Expression Grammars (via PEG.js) which is much faster than the Inside algorithm, but is unsuited to stochastic grammars such as those specified by a Bracery program
- syntactic sugar/hacks/apologies
- the Tracery-style expression
#name#
is parsed and implemented as&if{$name}then{&eval{$name}}else{~name}
. Tracery overloads the same namespace for symbol and variable names, and uses the variable if it's defined; this quasi-macro reproduces that behavior (almost) - braces around single-argument functions or symbols can be omitted, e.g.
$currency=&cap&plural~name
means the same as$currency={&cap{&plural{~name}}}
- variable and symbol names are case-insensitive
- the case used when a variable is referenced can be a shorthand for capitalization: you can use
~Nonterminal_name
as a shorthand for&cap{~nonterminal_name}
, and$Variable_name
for&cap{$variable_name}
- similarly,
~NONTERMINAL_NAME
is a shorthand for&uc{~nonterminal_name}
, and$VARIABLE_NAME
for&uc{$variable_name}
- the case used when a variable is referenced can be a shorthand for capitalization: you can use
- some Tracery modifier syntax works, e.g.
#symbol_name.capitalize#
instead of&cap{#symbol_name#}
- the syntax
[name=>value1|value2|value3|...]
is shorthand for$name={"e{[value1|value2|value3|...]}
and ensures that every occurrence of#name#
(or&eval{$name}
) will be expanded from an independently-sampled one of the values
- the Tracery-style expression
Most/all of these features are exercised in the file test/basic.js.
Advanced features
Bracery contains some advanced features designed to allow Bracery messages to be sequenced (as in a Markov chain). These are described in the accompanying file MESSAGES.md.