author |
Steve Losh <steve@stevelosh.com> |
date |
Wed, 21 Jun 2023 15:21:12 -0400 |
parents |
15eb4bbf6d54 |
children |
(none) |
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8"/>
<title>Usage / Chancery</title>
<link rel="stylesheet" href="../_dmedia/tango.css"/>
<link rel="stylesheet/less" type="text/css" href="../_dmedia/style.less"/>
<script src="../_dmedia/less.js" type="text/javascript">
</script>
</head>
<body class="content">
<div class="wrap">
<header><h1><a href="..">Chancery</a></h1></header>
<div class="markdown">
<h1 id="usage"><a href="">Usage</a></h1><p>Chancery is a library for procedurally generating text and data in Common
Lisp. It's heavily inspired by <a href="http://www.crystalcodepalace.com/traceryTut.html">Tracery</a>, and is essentially just some Lispy
syntactic sugar for writing <a href="https://en.wikipedia.org/wiki/Context-free_grammar">grammars</a>.</p>
<div class="toc">
<ul>
<li><a href="#rules">Rules</a></li>
<li><a href="#text-generation">Text Generation</a></li>
<li><a href="#distributions">Distributions</a></li>
<li><a href="#single-generation">Single Generation</a></li>
<li><a href="#string-modifiers">String Modifiers</a></li>
<li><a href="#evaluation">Evaluation</a></li>
<li><a href="#reader-macros">Reader Macros</a></li>
<li><a href="#runtime-rule-creation">Runtime Rule Creation</a></li>
<li><a href="#tips">Tips</a></li>
<li><a href="#examples">Examples</a></li>
</ul></div>
<h2 id="rules">Rules</h2>
<p>Rules are the core construct Chancery uses to generate data, and can be created
with <code>define-rule</code>:</p>
<div class="codehilite"><pre><span/><span class="p">(</span><span class="nv">define-rule</span> <span class="nv">animal</span>
<span class="s">"cat"</span>
<span class="s">"dog"</span>
<span class="s">"mouse"</span><span class="p">)</span>
<span class="p">(</span><span class="nv">define-rule</span> <span class="nv">color</span>
<span class="s">"black"</span>
<span class="s">"white"</span>
<span class="s">"brown"</span>
<span class="s">"gray"</span><span class="p">)</span>
</pre></div>
<p>Rules are compiled into vanilla Lisp functions, so you can call them like you
would any other function.</p>
<div class="codehilite"><pre><span/><span class="p">(</span><span class="nb">loop</span> <span class="ss">:repeat</span> <span class="mi">10</span>
<span class="ss">:collect</span> <span class="p">(</span><span class="nb">list</span> <span class="p">(</span><span class="nv">color</span><span class="p">)</span> <span class="p">(</span><span class="nv">animal</span><span class="p">)))</span>
<span class="c1">; =></span>
<span class="p">((</span><span class="s">"gray"</span> <span class="s">"dog"</span><span class="p">)</span> <span class="p">(</span><span class="s">"white"</span> <span class="s">"mouse"</span><span class="p">)</span> <span class="p">(</span><span class="s">"gray"</span> <span class="s">"dog"</span><span class="p">)</span>
<span class="p">(</span><span class="s">"black"</span> <span class="s">"mouse"</span><span class="p">)</span> <span class="p">(</span><span class="s">"gray"</span> <span class="s">"cat"</span><span class="p">)</span> <span class="p">(</span><span class="s">"gray"</span> <span class="s">"cat"</span><span class="p">)</span>
<span class="p">(</span><span class="s">"white"</span> <span class="s">"mouse"</span><span class="p">)</span> <span class="p">(</span><span class="s">"white"</span> <span class="s">"dog"</span><span class="p">)</span> <span class="p">(</span><span class="s">"gray"</span> <span class="s">"mouse"</span><span class="p">)</span>
<span class="p">(</span><span class="s">"brown"</span> <span class="s">"cat"</span><span class="p">))</span>
</pre></div>
<p>Basic rules select one of their body terms at random and evaluate it according
to some special rules. Most kinds of objects (e.g. strings, keywords, numbers)
evaluate to themselves, but there are a few exceptions.</p>
<p>Symbols evaluate to <code>(funcall 'symbol)</code>, so rules can easily call other rules:</p>
<div class="codehilite"><pre><span/><span class="p">(</span><span class="nv">define-rule</span> <span class="nv">cat-name</span>
<span class="s">"fluffy"</span>
<span class="s">"whiskers"</span><span class="p">)</span>
<span class="p">(</span><span class="nv">define-rule</span> <span class="nv">dog-name</span>
<span class="s">"spot"</span>
<span class="s">"fido"</span>
<span class="s">"lassie"</span><span class="p">)</span>
<span class="p">(</span><span class="nv">define-rule</span> <span class="nv">animal-name</span>
<span class="nv">cat-name</span>
<span class="nv">dog-name</span><span class="p">)</span>
<span class="p">(</span><span class="nb">loop</span> <span class="ss">:repeat</span> <span class="mi">10</span> <span class="ss">:collect</span> <span class="p">(</span><span class="nv">animal-name</span><span class="p">))</span>
<span class="c1">; =></span>
<span class="p">(</span><span class="s">"spot"</span> <span class="s">"spot"</span> <span class="s">"fido"</span> <span class="s">"fluffy"</span> <span class="s">"fido"</span> <span class="s">"spot"</span>
<span class="s">"fluffy"</span> <span class="s">"spot"</span> <span class="s">"spot"</span> <span class="s">"lassie"</span><span class="p">)</span>
</pre></div>
<p>Lists recursively evaluate their members and return the result as a fresh list:</p>
<div class="codehilite"><pre><span/><span class="p">(</span><span class="nv">define-rule</span> <span class="nv">pet</span>
<span class="p">(</span><span class="nv">cat-name</span> <span class="nv">color</span> <span class="ss">:cat</span><span class="p">)</span>
<span class="p">(</span><span class="nv">dog-name</span> <span class="nv">color</span> <span class="ss">:dog</span><span class="p">))</span>
<span class="p">(</span><span class="nb">loop</span> <span class="ss">:repeat</span> <span class="mi">5</span> <span class="ss">:collect</span> <span class="p">(</span><span class="nv">pet</span><span class="p">))</span>
<span class="c1">; =></span>
<span class="p">((</span><span class="s">"fluffy"</span> <span class="s">"brown"</span> <span class="ss">:CAT</span><span class="p">)</span> <span class="p">(</span><span class="s">"fluffy"</span> <span class="s">"white"</span> <span class="ss">:CAT</span><span class="p">)</span>
<span class="p">(</span><span class="s">"fido"</span> <span class="s">"brown"</span> <span class="ss">:DOG</span><span class="p">)</span> <span class="p">(</span><span class="s">"lassie"</span> <span class="s">"white"</span> <span class="ss">:DOG</span><span class="p">)</span>
<span class="p">(</span><span class="s">"fluffy"</span> <span class="s">"black"</span> <span class="ss">:CAT</span><span class="p">))</span>
</pre></div>
<p>If you want to return a literal symbol or list from a rule you can <code>quote</code> it:</p>
<div class="codehilite"><pre><span/><span class="p">(</span><span class="nv">define-rule</span> <span class="nv">foo</span>
<span class="ss">'a</span>
<span class="o">'</span><span class="p">(</span><span class="nv">x</span> <span class="nv">y</span><span class="p">))</span>
<span class="p">(</span><span class="nb">loop</span> <span class="ss">:repeat</span> <span class="mi">5</span> <span class="ss">:collect</span> <span class="p">(</span><span class="nv">foo</span><span class="p">))</span>
<span class="c1">; =></span>
<span class="p">(</span><span class="nv">A</span> <span class="p">(</span><span class="nv">X</span> <span class="nv">Y</span><span class="p">)</span> <span class="p">(</span><span class="nv">X</span> <span class="nv">Y</span><span class="p">)</span> <span class="nv">A</span> <span class="nv">A</span><span class="p">)</span>
</pre></div>
<h2 id="text-generation">Text Generation</h2>
<p>Grammars are often used to generate strings, and Chancery has a few extra bits
of syntactic sugar to make it easy to generate readable text. <code>define-string</code>
can be used to define a rule function that stringifies its result before
returning it:</p>
<div class="codehilite"><pre><span/><span class="p">(</span><span class="nv">define-string</span> <span class="nv">animal</span> <span class="s">"cat"</span> <span class="s">"dog"</span> <span class="s">"mouse"</span><span class="p">)</span>
<span class="p">(</span><span class="nv">define-string</span> <span class="nv">name</span> <span class="s">"Alice"</span> <span class="s">"Bob"</span> <span class="s">"Carol"</span><span class="p">)</span>
<span class="p">(</span><span class="nv">define-string</span> <span class="nv">place</span> <span class="s">"mountain"</span> <span class="s">"forest"</span> <span class="s">"river"</span><span class="p">)</span>
<span class="p">(</span><span class="nv">define-string</span> <span class="nv">story</span>
<span class="p">(</span><span class="nv">name</span> <span class="s">"the"</span> <span class="nv">animal</span> <span class="s">"went to the"</span> <span class="nv">place</span><span class="p">)</span>
<span class="p">(</span><span class="s">"a friendly"</span> <span class="nv">animal</span> <span class="s">"lived near a"</span> <span class="nv">place</span><span class="p">))</span>
<span class="p">(</span><span class="nb">loop</span> <span class="ss">:repeat</span> <span class="mi">5</span> <span class="ss">:collect</span> <span class="p">(</span><span class="nv">story</span><span class="p">))</span>
<span class="c1">; =></span>
<span class="c1">; ("Alice the mouse went to the river"</span>
<span class="c1">; "Bob the cat went to the forest"</span>
<span class="c1">; "a friendly mouse lived near a river"</span>
<span class="c1">; "a friendly cat lived near a forest"</span>
<span class="c1">; "Bob the cat went to the river")</span>
</pre></div>
<p><code>define-string</code> works the same way as <code>define-rule</code>, except that the evaluation
of certain items is slightly different.</p>
<p>Strings evaluate to themselves. <code>NIL</code> evaluates to an empty string. Other
non-keyword symbols evaluate to procedure calls, as with <code>define-rule</code>.</p>
<p>Lists evaluate their arguments and concatenate them with one <code>#\Space</code> between
each. If you don't want a space between two items you can use the special
symbol <code>:.</code> to suppress it:</p>
<div class="codehilite"><pre><span/><span class="p">(</span><span class="nv">define-string</span> <span class="nv">foo</span> <span class="s">"x"</span> <span class="s">"y"</span><span class="p">)</span>
<span class="p">(</span><span class="nv">define-string</span> <span class="nv">with-spaces</span>
<span class="p">(</span><span class="nv">foo</span> <span class="nv">foo</span> <span class="nv">foo</span><span class="p">))</span>
<span class="p">(</span><span class="nv">define-string</span> <span class="nv">without-spaces</span>
<span class="p">(</span><span class="nv">foo</span> <span class="ss">:.</span> <span class="nv">foo</span> <span class="ss">:.</span> <span class="nv">foo</span><span class="p">))</span>
<span class="p">(</span><span class="nv">with-spaces</span><span class="p">)</span> <span class="c1">; => "x x y"</span>
<span class="p">(</span><span class="nv">without-spaces</span><span class="p">)</span> <span class="c1">; => "xyx"</span>
</pre></div>
<p>The special form <code>(quote ...)</code> evaluates to its argument, <em>without</em> stringifying
it. Note that this is one case where a <code>define-string</code> could return something
that isn't a string, so use it with care.</p>
<p>Everything else (except vectors, which we'll talk about shortly) evaluates to
the result of calling <code>princ-to-string</code> on it.</p>
<h2 id="distributions">Distributions</h2>
<p>By default each body term in a rule has an equal chance of being chosen.</p>
<p>Chancery also includes support for weighting the terms so some will be chosen
more often than others:</p>
<div class="codehilite"><pre><span/><span class="p">(</span><span class="nv">define-rule</span> <span class="p">(</span><span class="nv">metal</span> <span class="ss">:distribution</span> <span class="ss">:weighted</span><span class="p">)</span>
<span class="p">(</span><span class="mi">10</span> <span class="ss">:iron</span><span class="p">)</span>
<span class="p">(</span><span class="mi">5</span> <span class="ss">:steel</span><span class="p">)</span>
<span class="p">(</span><span class="mi">2</span> <span class="ss">:silver</span><span class="p">)</span>
<span class="p">(</span><span class="mi">1</span> <span class="ss">:gold</span><span class="p">)</span>
<span class="p">(</span><span class="mi">1</span> <span class="ss">:platinum</span><span class="p">))</span>
</pre></div>
<p>Weighting each term by hand can be tedious. Chancery can automatically
calculate weights based on the order of the body terms according to <a href="https://en.wikipedia.org/wiki/Zipf's_law">Zipf's
law</a>:</p>
<div class="codehilite"><pre><span/><span class="p">(</span><span class="nv">define-rule</span> <span class="p">(</span><span class="nv">armor-type</span> <span class="ss">:distribution</span> <span class="ss">:zipf</span><span class="p">)</span>
<span class="ss">:scale-mail</span>
<span class="ss">:chain-mail</span>
<span class="ss">:banded-mail</span>
<span class="ss">:plate-mail</span><span class="p">)</span>
</pre></div>
<p>The Zipf distribution can take an argument <code>exponent</code>, which is the exponent
characterizing the distribution. The default is <code>1.0</code>, and larger exponents
will result in the earlier terms being chosen more often:</p>
<div class="codehilite"><pre><span/><span class="p">(</span><span class="nv">define-rule</span> <span class="p">(</span><span class="nv">foo</span> <span class="ss">:distribution</span> <span class="ss">:zipf</span><span class="p">)</span>
<span class="ss">:a</span> <span class="ss">:b</span> <span class="ss">:c</span> <span class="ss">:d</span> <span class="ss">:e</span><span class="p">)</span>
<span class="p">(</span><span class="nv">define-rule</span> <span class="p">(</span><span class="nv">bar</span> <span class="ss">:distribution</span> <span class="p">(</span><span class="ss">:zipf</span> <span class="ss">:exponent</span> <span class="mf">3.0</span><span class="p">))</span>
<span class="ss">:a</span> <span class="ss">:b</span> <span class="ss">:c</span> <span class="ss">:d</span> <span class="ss">:e</span><span class="p">)</span>
<span class="p">(</span><span class="nv">define-rule</span> <span class="p">(</span><span class="nv">baz</span> <span class="ss">:distribution</span> <span class="p">(</span><span class="ss">:zipf</span> <span class="ss">:exponent</span> <span class="mf">0.25</span><span class="p">))</span>
<span class="ss">:a</span> <span class="ss">:b</span> <span class="ss">:c</span> <span class="ss">:d</span> <span class="ss">:e</span><span class="p">)</span>
<span class="p">(</span><span class="nb">count</span> <span class="ss">:a</span> <span class="p">(</span><span class="nb">loop</span> <span class="ss">:repeat</span> <span class="mi">1000</span> <span class="ss">:collect</span> <span class="p">(</span><span class="nv">foo</span><span class="p">)))</span>
<span class="c1">; => 413</span>
<span class="p">(</span><span class="nb">count</span> <span class="ss">:a</span> <span class="p">(</span><span class="nb">loop</span> <span class="ss">:repeat</span> <span class="mi">1000</span> <span class="ss">:collect</span> <span class="p">(</span><span class="nv">bar</span><span class="p">)))</span>
<span class="c1">; => 831</span>
<span class="p">(</span><span class="nb">count</span> <span class="ss">:a</span> <span class="p">(</span><span class="nb">loop</span> <span class="ss">:repeat</span> <span class="mi">1000</span> <span class="ss">:collect</span> <span class="p">(</span><span class="nv">baz</span><span class="p">)))</span>
<span class="c1">; => 257</span>
</pre></div>
<h2 id="single-generation">Single Generation</h2>
<p>Sometimes it can be handy to evaluate a Chancery expression without defining an
actual rule. The <code>generate</code> macro evaluates vanilla Chancery expressions, and
<code>generate-string</code> evaluates Chancery string expressions:</p>
<div class="codehilite"><pre><span/><span class="p">(</span><span class="nv">define-rule</span> <span class="nv">x</span> <span class="mi">1</span> <span class="mi">2</span> <span class="mi">3</span><span class="p">)</span>
<span class="p">(</span><span class="nv">define-rule</span> <span class="nv">animal</span> <span class="s">"cat"</span> <span class="s">"dog"</span> <span class="s">"mouse"</span><span class="p">)</span>
<span class="p">(</span><span class="nv">generate</span> <span class="ss">:foo</span><span class="p">)</span> <span class="c1">; => :FOO</span>
<span class="p">(</span><span class="nv">generate</span> <span class="nv">x</span><span class="p">)</span> <span class="c1">; => 2</span>
<span class="p">(</span><span class="nv">generate</span> <span class="p">(</span><span class="nv">x</span> <span class="nv">x</span> <span class="nv">x</span><span class="p">))</span> <span class="c1">; => (2 2 1)</span>
<span class="p">(</span><span class="nv">generate-string</span> <span class="ss">:foo</span><span class="p">)</span> <span class="c1">; => "FOO"</span>
<span class="p">(</span><span class="nv">generate-string</span> <span class="nv">x</span><span class="p">)</span> <span class="c1">; => "1"</span>
<span class="p">(</span><span class="nv">generate-string</span> <span class="p">(</span><span class="s">"fuzzy"</span> <span class="nv">animal</span><span class="p">))</span> <span class="c1">; => "fuzzy mouse"</span>
</pre></div>
<h2 id="string-modifiers">String Modifiers</h2>
<p>Procedurally generating readable text can be tricky, so Chancery includes some
syntax and a few helper functions to make your life easier.</p>
<p>Inside a <code>define-string</code> vectors are evaluated specially. The first element of
the vector is evaluated as usual in <code>define-string</code>. All remaining elements in
the vector are treated as function names, and the result is threaded through
each function in turn. Finally, the resulting value will be stringified. For
example:</p>
<div class="codehilite"><pre><span/><span class="p">(</span><span class="nv">define-rule</span> <span class="nv">animal</span> <span class="s">"cat"</span> <span class="s">"dog"</span> <span class="s">"mouse"</span><span class="p">)</span>
<span class="p">(</span><span class="nv">generate-string</span> <span class="p">(</span><span class="s">"the"</span> <span class="nv">animal</span> <span class="s">"ran"</span><span class="p">))</span>
<span class="c1">; =></span>
<span class="c1">; "the cat ran"</span>
<span class="p">(</span><span class="nv">generate-string</span> <span class="p">(</span><span class="s">"the"</span> <span class="o">#(</span><span class="nv">animal</span> <span class="nb">string-upcase</span><span class="p">)</span> <span class="s">"ran"</span><span class="p">))</span>
<span class="c1">; =></span>
<span class="c1">; "the MOUSE ran"</span>
</pre></div>
<p>Chancery defines a few helpful functions for generating English text:</p>
<ul>
<li><code>s</code> pluralizes its argument.</li>
<li><code>a</code> adds an indefinite article ("a" or "an") to the front of its argument.</li>
<li><code>cap</code> capitalizes the first letter of its argument.</li>
<li><code>cap-all</code> capitalizes each word in its argument.)</li>
<li><code>pos</code> makes its argument possessive by adding an apostrophe (and possibly an s).</li>
</ul>
<p>These are just normal Lisp functions that happen to be useful as modifiers:</p>
<div class="codehilite"><pre><span/><span class="p">(</span><span class="nv">define-string</span> <span class="nv">animal</span> <span class="s">"cat"</span> <span class="s">"aardvark"</span> <span class="s">"fly"</span><span class="p">)</span>
<span class="p">(</span><span class="nb">loop</span> <span class="ss">:repeat</span> <span class="mi">5</span>
<span class="ss">:collect</span> <span class="p">(</span><span class="nv">generate-string</span>
<span class="p">(</span><span class="s">"a bunch of"</span> <span class="o">#(</span><span class="nv">animal</span> <span class="nv">s</span><span class="p">))))</span>
<span class="c1">; =></span>
<span class="c1">; ("a bunch of flies" "a bunch of flies" "a bunch of aardvarks"</span>
<span class="c1">; "a bunch of cats" "a bunch of aardvarks")</span>
<span class="p">(</span><span class="nb">loop</span> <span class="ss">:repeat</span> <span class="mi">5</span>
<span class="ss">:collect</span> <span class="p">(</span><span class="nv">generate-string</span>
<span class="p">(</span><span class="o">#(</span><span class="nv">animal</span> <span class="nv">a</span> <span class="nv">cap</span><span class="p">)</span> <span class="s">"is a good pet"</span><span class="p">)))</span>
<span class="c1">; =></span>
<span class="c1">; ("A cat is a good pet" "A fly is a good pet" "A fly is a good pet"</span>
<span class="c1">; "A cat is a good pet" "An aardvark is a good pet")</span>
</pre></div>
<p>English is a messy language. Pull requests to improve these functions (or add
more useful ones) are welcome.</p>
<h2 id="evaluation">Evaluation</h2>
<p>Sometimes it can be useful to switch out of "Chancery evaluation" and back into
normal Lisp evaluation. The <code>(eval ...)</code> special form can be used to do this in
Chancery rules and string rules:</p>
<div class="codehilite"><pre><span/><span class="p">(</span><span class="nv">define-rule</span> <span class="nv">treasure</span>
<span class="ss">:weapon</span>
<span class="ss">:armor</span>
<span class="p">((</span><span class="nb">eval</span> <span class="p">(</span><span class="nb">random</span> <span class="mi">100</span><span class="p">))</span> <span class="ss">:gold</span><span class="p">))</span>
<span class="p">(</span><span class="nb">loop</span> <span class="ss">:repeat</span> <span class="mi">5</span> <span class="ss">:collect</span> <span class="p">(</span><span class="nv">treasure</span><span class="p">))</span>
<span class="c1">; =></span>
<span class="c1">; ((93 :GOLD)</span>
<span class="c1">; :WEAPON</span>
<span class="c1">; (83 :GOLD)</span>
<span class="c1">; :WEAPON</span>
<span class="c1">; :ARMOR)</span>
<span class="p">(</span><span class="nv">define-string</span> <span class="nv">animal</span> <span class="s">"cat"</span> <span class="s">"dog"</span><span class="p">)</span>
<span class="p">(</span><span class="nv">define-string</span> <span class="nv">story</span>
<span class="p">(</span><span class="s">"The"</span> <span class="nv">animal</span> <span class="s">"had"</span> <span class="p">(</span><span class="nb">eval</span> <span class="p">(</span><span class="nb">+</span> <span class="mi">2</span> <span class="p">(</span><span class="nb">random</span> <span class="mi">3</span><span class="p">)))</span> <span class="s">"friends."</span><span class="p">))</span>
<span class="p">(</span><span class="nb">loop</span> <span class="ss">:repeat</span> <span class="mi">5</span> <span class="ss">:collect</span> <span class="p">(</span><span class="nv">story</span><span class="p">))</span>
<span class="c1">; =></span>
<span class="c1">; ("The dog had 3 friends." "The cat had 2 friends." "The dog had 3 friends."</span>
<span class="c1">; "The cat had 4 friends." "The dog had 2 friends.")</span>
</pre></div>
<p>You can think of <code>eval</code> and <code>generate</code> as two sides of the same coin, kind of
like quasiquote's backquote and comma. <code>eval</code> flips from Chancery evaluation to
Lisp evaluation, and <code>generate</code> flips in the other direction.</p>
<h2 id="reader-macros">Reader Macros</h2>
<p>Chancery provides four reader macros you can use to make defining grammars
even more concise. It uses <a href="https://common-lisp.net/project/named-readtables/">named-readtables</a> to keep them safely stashed
away unless you want them.</p>
<p>The first reader macro is <code>[...]</code> to replace the standard vector <code>#(...)</code>
syntax, to make string modifiers stand out more:</p>
<div class="codehilite"><pre><span/><span class="p">(</span><span class="nv">named-readtables:in-readtable</span> <span class="ss">:chancery</span><span class="p">)</span>
<span class="p">(</span><span class="nv">define-string</span> <span class="nv">animal</span> <span class="s">"cat"</span> <span class="s">"dog"</span><span class="p">)</span>
<span class="p">(</span><span class="nv">generate-string</span> <span class="p">(</span><span class="s">"a few"</span> <span class="nv">[animal</span> <span class="nv">s]</span><span class="p">))</span>
<span class="c1">; => "a few cats"</span>
</pre></div>
<p>The reader macro <code>!form</code> expands to <code>(eval form)</code>:</p>
<div class="codehilite"><pre><span/><span class="p">(</span><span class="nv">named-readtables:in-readtable</span> <span class="ss">:chancery</span><span class="p">)</span>
<span class="p">(</span><span class="nv">define-rule</span> <span class="nv">treasure</span>
<span class="ss">:weapon</span>
<span class="ss">:armor</span>
<span class="p">(</span><span class="nv">!</span><span class="p">(</span><span class="nb">random</span> <span class="mi">100</span><span class="p">)</span> <span class="ss">:gold</span><span class="p">))</span>
<span class="p">(</span><span class="nb">loop</span> <span class="ss">:repeat</span> <span class="mi">5</span> <span class="ss">:collect</span> <span class="p">(</span><span class="nv">treasure</span><span class="p">))</span>
<span class="c1">; =></span>
<span class="c1">; (:ARMOR (53 :GOLD) (49 :GOLD) :ARMOR :WEAPON)</span>
</pre></div>
<p>The reader macro <code>@form</code> expands to <code>(generate form)</code>, and <code>$form</code> expands to
<code>(generate-string form)</code>. Note that none of th</p>
<div class="codehilite"><pre><span/><span class="p">(</span><span class="nv">named-readtables:in-readtable</span> <span class="ss">:chancery</span><span class="p">)</span>
<span class="p">(</span><span class="nv">define-string</span> <span class="nv">animal</span> <span class="s">"cat"</span> <span class="s">"dog"</span><span class="p">)</span>
<span class="nv">$</span><span class="p">(</span><span class="s">"the"</span> <span class="nv">animal</span> <span class="s">"ran"</span><span class="p">)</span>
<span class="c1">; => "the cat ran"</span>
</pre></div>
<p>Together these let you jump in and out of Chancery-style evaluation as needed:</p>
<div class="codehilite"><pre><span/><span class="p">(</span><span class="nv">named-readtables:in-readtable</span> <span class="ss">:chancery</span><span class="p">)</span>
<span class="p">(</span><span class="nv">define-string</span> <span class="nv">animal</span> <span class="s">"cat"</span> <span class="s">"dog"</span><span class="p">)</span>
<span class="p">(</span><span class="nv">define-string</span> <span class="nv">story</span>
<span class="p">(</span><span class="s">"the"</span> <span class="nv">animal</span> <span class="s">"went to sleep"</span><span class="p">)</span>
<span class="nv">!</span><span class="p">(</span><span class="k">let</span> <span class="p">((</span><span class="nv">pet</span> <span class="p">(</span><span class="nv">animal</span><span class="p">)))</span>
<span class="nv">$</span><span class="p">(</span><span class="s">"the"</span> <span class="nv">!pet</span> <span class="s">"was a good"</span> <span class="nv">!pet</span><span class="p">)))</span>
<span class="p">(</span><span class="nb">loop</span> <span class="ss">:repeat</span> <span class="mi">4</span> <span class="ss">:collect</span> <span class="p">(</span><span class="nv">story</span><span class="p">))</span>
<span class="c1">; =></span>
<span class="c1">; ("the dog was a good dog" "the cat was a good cat"</span>
<span class="c1">; "the dog was a good dog" "the cat went to sleep")</span>
</pre></div>
<p>This last example is a little tricky, but it helps to think of <code>$</code> and <code>@</code> as
quasiquote's backquote, and <code>!</code> as comma. Flipping back and forth between Lisp
and Chancery can let you define some really interesting grammars.</p>
<h2 id="runtime-rule-creation">Runtime Rule Creation</h2>
<p>All of the Chancery things we've been using so far have been macros. If you
want to create rules at runtime you can use their function counterparts:</p>
<ul>
<li><code>create-rule</code> takes a list of expressions and options, and returns a rule
function.</li>
<li><code>create-string</code> takes a list of expressions and options, and returns a string
function.</li>
<li><code>invoke-generate</code> takes an expression and evaluates it.</li>
<li><code>invoke-generate-string</code> takes an expression and evaluates it and stringifies
the result.</li>
</ul>
<p>For example:</p>
<div class="codehilite"><pre><span/><span class="p">(</span><span class="n">define</span><span class="o">-</span><span class="n">rule</span> <span class="nl">foo</span> <span class="p">:</span><span class="nl">a</span> <span class="p">:</span><span class="nl">b</span> <span class="p">:</span><span class="n">c</span><span class="p">)</span>
<span class="p">(</span><span class="n">foo</span><span class="p">)</span>
<span class="p">;</span> <span class="n">versus</span>
<span class="p">(</span><span class="n">funcall</span> <span class="p">(</span><span class="n">create</span><span class="o">-</span><span class="n">rule</span> <span class="p">(</span><span class="nl">list</span> <span class="p">:</span><span class="nl">a</span> <span class="p">:</span><span class="nl">b</span> <span class="p">:</span><span class="n">c</span><span class="p">)))</span>
<span class="p">(</span><span class="n">define</span><span class="o">-</span><span class="n">string</span> <span class="p">(</span><span class="nl">bar</span> <span class="p">:</span><span class="nl">distribution</span> <span class="p">:</span><span class="n">zipf</span><span class="p">)</span>
<span class="p">(</span><span class="s">"one"</span> <span class="n">foo</span><span class="p">)</span>
<span class="p">(</span><span class="s">"two"</span> <span class="p">[</span><span class="n">foo</span> <span class="n">s</span><span class="p">]))</span>
<span class="p">(</span><span class="n">bar</span><span class="p">)</span>
<span class="p">;</span> <span class="n">versus</span>
<span class="p">(</span><span class="n">funcall</span> <span class="p">(</span><span class="n">create</span><span class="o">-</span><span class="n">string</span> <span class="p">(</span><span class="n">list</span> <span class="err">'</span><span class="p">(</span><span class="s">"one"</span> <span class="n">foo</span><span class="p">)</span>
<span class="err">'</span><span class="p">(</span><span class="s">"two"</span> <span class="p">[</span><span class="n">foo</span> <span class="n">s</span><span class="p">]))</span>
<span class="o">:</span><span class="nl">distribution</span> <span class="p">:</span><span class="n">zipf</span><span class="p">))</span>
</pre></div>
<h2 id="tips">Tips</h2>
<p>Chancery aims to be a very thin layer on top of Lisp. Chancery rules are just
vanilla Lisp functions—you can <code>describe</code> them, <code>disassemble</code> them, <code>trace</code>
them, <code>funcall</code> them, and anything else you might normally want to do with
functions. Similarly, Chancery string modifiers are just unary functions—you
can create your own, or even use built-in Lisp functions like <code>string-upcase</code>.</p>
<p>Remember that you can flip back and forth between Chancery evaluation and normal
Lisp evaluation. Sometimes it's easier to just do a quick <code>!(if foo then else)</code>
than to make Yet Another Rule. Use your own judgement to determine when a rule
is getting too hairy and needs to be split into simpler parts, just like you
would for any other Lisp function.</p>
<h2 id="examples">Examples</h2>
<p>If you want some less trivial examples than the ones seen here, you might want
to take a look at some of the Twitter bots I've built with Chancery:</p>
<ul>
<li><a href="http://twitter.com/git_commands">@git_commands</a>
(<a href="https://github.com/sjl/magitek/blob/master/src/robots/git-commands.lisp">code</a>)
tweets procedurally-generated Git command summaries.</li>
<li><a href="http://twitter.com/lisp_talks">@lisp_talks</a>
(<a href="https://github.com/sjl/magitek/blob/master/src/robots/lisp-talks.lisp">code</a>)
tweets random topics for Lisp talks.</li>
<li><a href="http://twitter.com/rpg_shopkeeper">@rpg_shopkeeper</a>
(<a href="https://github.com/sjl/magitek/blob/master/src/robots/rpg-shopkeeper.lisp">code</a>)
generates random fantasy RPG items, and sells them for appropriate prices.</li>
</ul>
</div>
<footer><p><i>Made with Lisp and love by <a href="http://stevelosh.com/">Steve Losh</a> in Reykjavík, Iceland.</i></p>
<script>
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
})(window,document,'script','//www.google-analytics.com/analytics.js','ga');
ga('create', 'UA-15328874-3', 'auto');
ga('send', 'pageview');
</script></footer>
</div>
</body>
</html>