53d4a4db967f

Finish the damn docs
[view raw] [browse files]
author Steve Losh <steve@stevelosh.com>
date Fri, 03 Nov 2017 01:05:31 -0400
parents ca108d2f3b79
children d0c6327552cb
branches/tags (none)
files docs/02-usage.markdown docs/03-reference.markdown docs/index.markdown examples/fantasy-weapons.lisp examples/git-commands.js examples/git-commands.lisp examples/story.lisp src/chancery.lisp

Changes

--- a/docs/02-usage.markdown	Thu Nov 02 22:32:50 2017 -0400
+++ b/docs/02-usage.markdown	Fri Nov 03 01:05:31 2017 -0400
@@ -40,9 +40,9 @@
      ("white" "mouse") ("white" "dog") ("gray" "mouse")
      ("brown" "cat"))
 
-Basic rules select one of their body terms at random and evaluate it.  Most
-kinds of objects (e.g. strings, keywords, numbers) evaluate to themselves, but
-there are a few exceptions.
+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.
 
 Symbols evaluate to `(funcall 'symbol)`, so rules can easily call other rules:
 
@@ -89,6 +89,60 @@
     ; =>
     (A (X Y) (X Y) A A)
 
+Text Generation
+---------------
+
+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.  `define-string`
+can be used to define a rule function that stringifies its result before
+returning it:
+
+    :::lisp
+    (define-string animal "cat" "dog" "mouse")
+    (define-string name "Alice" "Bob" "Carol")
+    (define-string place "mountain" "forest" "river")
+
+    (define-string story
+      (name "the" animal "went to the" place)
+      ("a friendly" animal "lived near a" place))
+
+    (loop :repeat 5 :collect (story))
+    ; =>
+    ; ("Alice the mouse went to the river"
+    ;  "Bob the cat went to the forest"
+    ;  "a friendly mouse lived near a river"
+    ;  "a friendly cat lived near a forest"
+    ;  "Bob the cat went to the river")
+
+`define-string` works the same way as `define-rule`, except that the evaluation
+of certain items is slightly different.
+
+Strings evaluate to themselves.  `NIL` evaluates to an empty string.  Other
+non-keyword symbols evaluate to procedure calls, as with `define-rule`.
+
+Lists evaluate their arguments and concatenate them with one `#\Space` between
+each.  If you don't want a space between two items you can use the special
+symbol `:.` to suppress it:
+
+    :::lisp
+    (define-string foo "x" "y")
+
+    (define-string with-spaces
+      (foo foo foo))
+
+    (define-string without-spaces
+      (foo :. foo :. foo))
+
+    (with-spaces)    ; => "x x y"
+    (without-spaces) ; => "xyx"
+
+The special form `(quote ...)` evaluates to its argument, *without* stringifying
+it.  Note that this is one case where a `define-string` could return something
+that isn't a string, so use it with care.
+
+Everything else (except vectors, which we'll talk about shortly) evaluates to
+the result of calling `princ-to-string` on it.
+
 Distributions
 -------------
 
@@ -116,16 +170,264 @@
       :banded-mail
       :plate-mail)
 
+The Zipf distribution can itself take an argument `exponent`, which is the
+exponent characterizing the distribution.  The default is `1.0`, and larger
+exponents will result in the earlier terms being chosen more often:
+
+    :::lisp
+    (define-rule (foo :distribution :zipf)
+      :a :b :c :d :e)
+
+    (define-rule (bar :distribution (:zipf :exponent 3.0))
+      :a :b :c :d :e)
+
+    (define-rule (baz :distribution (:zipf :exponent 0.25))
+      :a :b :c :d :e)
+
+    (count :a (loop :repeat 1000 :collect (foo)))
+    ; => 413
+
+    (count :a (loop :repeat 1000 :collect (bar)))
+    ; => 831
+
+    (count :a (loop :repeat 1000 :collect (baz)))
+    ; => 257
+
 [zipf]: https://en.wikipedia.org/wiki/Zipf's_law
 
-Text Generation
----------------
+Single Generation
+-----------------
+
+Sometimes it can be handy to evaluate a Chancery expression without defining an
+actual rule.  The `generate` macro evaluates vanilla Chancery expressions, and
+`generate-string` evaluates Chancery string expressions:
+
+    :::lisp
+    (define-rule x 1 2 3)
+    (define-rule animal "cat" "dog" "mouse")
+
+    (generate :foo)    ; => :FOO
+    (generate x)       ; => 2
+    (generate (x x x)) ; => (2 2 1)
+
+    (generate-string :foo)             ; => "FOO"
+    (generate-string x)                ; => "1"
+    (generate-string ("fuzzy" animal)) ; => "fuzzy mouse"
+
+String Modifiers
+----------------
+
+Procedurally generating readable text can be tricky, so Chancery includes some
+syntax and a few helper functions to make your life easier.
+
+Inside a `define-string` vectors are evaluated specially.  The first element of
+the vector is evaluated as usual in `define-string`.  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:
+
+    :::lisp
+    (define-rule animal "cat" "dog" "mouse")
 
-Modifiers
----------
+    (generate-string ("the" animal "ran"))
+    ; =>
+    ; "the cat ran"
+
+    (generate-string ("the" #(animal string-upcase) "ran"))
+    ; =>
+    ; "the MOUSE ran"
+
+Chancery defines a few helpful functions for generating English text:
+
+* `s` pluralizes its argument.
+* `a` adds an indefinite article ("a" or "an") to the front of its argument.
+* `cap` capitalizes the first letter of its argument.
+* `cap-all` capitalizes each word in its argument.)
+* `pos` makes its argument possessive by adding an apostrophe (and possibly an s).
+
+These are just normal Lisp functions that happen to be useful as modifiers:
+
+    :::lisp
+    (define-string animal "cat" "aardvark" "fly")
+
+    (loop :repeat 5
+          :collect (generate-string
+                     ("a bunch of" #(animal s))))
+    ; =>
+    ; ("a bunch of flies" "a bunch of flies" "a bunch of aardvarks"
+    ;  "a bunch of cats" "a bunch of aardvarks")
+
+    (loop :repeat 5
+          :collect (generate-string
+                     (#(animal a cap) "is a good pet")))
+    ; =>
+    ; ("A cat is a good pet" "A fly is a good pet" "A fly is a good pet"
+    ;  "A cat is a good pet" "An aardvark is a good pet")
+
+English is a messy language.  Pull requests to improve these functions (or add
+more useful ones) are welcome.
 
 Evaluation
 ----------
 
+Sometimes it can be useful to switch out of "Chancery evaluation" and back into
+normal Lisp evaluation.  The `(eval ...)` special form can be used to do this in
+Chancery rules and string rules:
+
+    :::lisp
+    (define-rule treasure
+      :weapon
+      :armor
+      ((eval (random 100)) :gold))
+
+    (loop :repeat 5 :collect (treasure))
+    ; =>
+    ; ((93 :GOLD)
+    ;  :WEAPON
+    ;  (83 :GOLD)
+    ;  :WEAPON
+    ;  :ARMOR)
+
+    (define-string animal "cat" "dog")
+    (define-string story
+      ("The" animal "had" (eval (+ 2 (random 3))) "friends."))
+
+    (loop :repeat 5 :collect (story))
+    ; =>
+    ; ("The dog had 3 friends." "The cat had 2 friends." "The dog had 3 friends."
+    ;  "The cat had 4 friends." "The dog had 2 friends.")
+
+You can think of `eval` and `generate` as two sides of the same coin, kind of
+like quasiquote's backquote and comma.  `eval` flips from Chancery evaluation to
+Lisp evaluation, and `generate` flips in the other direction.
+
 Reader Macros
 -------------
+
+Chancery provides four reader macros you can use to make defining grammars
+even more concise.  It uses [named-readtables][] to keep them safely stashed
+away unless you want them.
+
+The first reader macro is `[...]` to replace the standard vector `#(...)`
+syntax, to make string modifiers stand out more:
+
+    :::lisp
+    (named-readtables:in-readtable :chancery)
+
+    (define-string animal "cat" "dog")
+
+    (generate-string ("a few" [animal s]))
+    ; => "a few cats"
+
+The reader macro `!form` expands to `(eval form)`:
+
+    :::lisp
+    (named-readtables:in-readtable :chancery)
+
+    (define-rule treasure
+      :weapon
+      :armor
+      (!(random 100) :gold))
+
+    (loop :repeat 5 :collect (treasure))
+    ; =>
+    ; (:ARMOR (53 :GOLD) (49 :GOLD) :ARMOR :WEAPON)
+
+The reader macro `@form` expands to `(generate form)`, and `$form` expands to
+`(generate-string form)`.  Note that none of th
+
+    :::lisp
+    (named-readtables:in-readtable :chancery)
+
+    (define-string animal "cat" "dog")
+
+    $("the" animal "ran")
+    ; => "the cat ran"
+
+Together these let you jump in and out of Chancery-style evaluation as needed:
+
+    :::lisp
+    (named-readtables:in-readtable :chancery)
+
+    (define-string animal "cat" "dog")
+
+    (define-string story
+      ("the" animal "went to sleep")
+      !(let ((pet (animal)))
+         $("the" !pet "was a good" !pet)))
+
+    (loop :repeat 4 :collect (story))
+    ; =>
+    ; ("the dog was a good dog" "the cat was a good cat"
+    ;  "the dog was a good dog" "the cat went to sleep")
+
+This last example is a little tricky, but it helps to think of `$` and `@` as
+quasiquote's backquote, and `!` as comma.  Flipping back and forth between Lisp
+and Chancery can let you define some really interesting grammars.
+
+
+
+
+
+[named-readtables]: https://common-lisp.net/project/named-readtables/
+
+Runtime Rule Creation
+---------------------
+
+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:
+
+* `create-rule` takes a list of expressions and options, and returns a rule
+  function.
+* `create-string` takes a list of expressions and options, and returns a string
+  function.
+* `invoke-generate` takes an expression and evaluates it.
+* `invoke-generate-string` takes an expression and evaluates it and stringifies
+  the result.
+
+For example:
+
+    (define-rule foo :a :b :c)
+    (foo)
+    ; versus
+    (funcall (create-rule (list :a :b :c)))
+
+    (define-string (bar :distribution :zipf)
+      ("one" foo)
+      ("two" [foo s]))
+    (bar)
+    ; versus
+    (funcall (create-string (list '("one" foo)
+                                  '("two" [foo s]))
+               :distribution :zipf))
+
+Tips
+----
+
+Chancery aims to be a very thin layer on top of Lisp.  Chancery rules are just
+vanilla Lisp functions—you can `describe` them, `disassemble` them, `trace`
+them, `funcall` 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 `string-upcase`.
+
+Remember that you can flip back and forth between Chancery evaluation and normal
+Lisp evaluation.  Sometimes it's easier to just do a quick `!(if foo then else)`
+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.
+
+Examples
+--------
+
+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:
+
+* [@git\_commands](http://twitter.com/git_commands)
+  ([code](https://github.com/sjl/magitek/blob/master/src/robots/git-commands.lisp))
+  tweets procedurally-generated Git command summaries.
+* [@lisp\_talks](http://twitter.com/lisp_talks)
+  ([code](https://github.com/sjl/magitek/blob/master/src/robots/lisp-talks.lisp))
+  tweets random topics for Lisp talks.
+* [@rpg\_shopkeeper](http://twitter.com/rpg_shopkeeper)
+  ([code](https://github.com/sjl/magitek/blob/master/src/robots/rpg-shopkeeper.lisp))
+  generates random fantasy RPG items, and sells them for appropriate prices.
--- a/docs/03-reference.markdown	Thu Nov 02 22:32:50 2017 -0400
+++ b/docs/03-reference.markdown	Fri Nov 03 01:05:31 2017 -0400
@@ -12,6 +12,10 @@
 
 ## Package `CHANCERY`
 
+### `*RANDOM*` (variable)
+
+The random number generation function to use (default: `CL:RANDOM`).
+
 ### `A` (function)
 
     (A STRING)
@@ -30,25 +34,163 @@
 
 Capitalize each word of `string`.
 
+### `CREATE-RULE` (function)
+
+    (CREATE-RULE EXPRESSIONS &REST OPTIONS)
+
+Return a function that will return random elements of `expressions`.
+
+  `options` should be of the form:
+
+    (&key documentation (distribution :uniform) (arguments '()))
+
+  `documentation` will be used as a docstring for the resulting function.
+
+  `distribution` denotes the distribution of elements returned.
+
+  `arguments` is the arglist of the resulting function.
+
+  Examples:
+
+    (create-rule (list :blue :red :green))
+
+    (create-rule (list :copper :silver :gold :platinum)
+      :documentation "Return a random metal."
+      :distribution :zipf)
+
+  See the full documentation for more information.
+
+  
+
+### `CREATE-STRING` (function)
+
+    (CREATE-STRING EXPRESSIONS &REST OPTIONS)
+
+Return a function that will return random stringified elements of `expressions`.
+
+  `options` should be of the form:
+
+    (&key documentation (distribution :uniform) (arguments '()))
+
+  `documentation` will be used as a docstring for the resulting function.
+
+  `distribution` denotes the distribution of elements returned.
+
+  `arguments` is the arglist of the resulting function.
+
+  Examples:
+
+    (create-string (list "white" "gray" "black"))
+
+    (create-string '((100 (color "cat"))
+                     (100 (color "dog"))
+                     (100 (color "dragon")))
+      :distribution :weighted)
+
+  See the full documentation for more information.
+
+  
+
 ### `DEFINE-RULE` (macro)
 
     (DEFINE-RULE NAME-AND-OPTIONS &REST EXPRESSIONS)
 
+Define a function that will return random elements of `expressions`.
+
+  `name-and-options` should be of the form:
+
+    (name &key documentation (distribution :uniform) (arguments '()))
+
+  If no options are needed a bare symbol can be given.
+
+  `name` is the symbol under which the resulting function will be defined.
+
+  `documentation` will be used as a docstring for the resulting function.
+
+  `distribution` denotes the distribution of elements returned.
+
+  `arguments` is the arglist of the resulting function.
+
+  Examples:
+
+    (define-rule color
+      :blue
+      :green
+      :red)
+
+    (define-rule (metal :documentation "Return a random metal."
+                        :distribution :zipf)
+      :copper
+      :silver
+      :gold
+      :platinum)
+
+  See the full documentation for more information.
+
+  
+
 ### `DEFINE-STRING` (macro)
 
     (DEFINE-STRING NAME-AND-OPTIONS &REST EXPRESSIONS)
 
-### `GEN` (macro)
+Define a function that will return random stringified elements of `expressions`.
+
+  `name-and-options` should be of the form:
+
+    (name &key documentation (distribution :uniform) (arguments '()))
+
+  If no options are needed a bare symbol can be given.
+
+  `name` is the symbol under which the resulting function will be defined.
+
+  `documentation` will be used as a docstring for the resulting function.
+
+  `distribution` denotes the distribution of elements returned.
+
+  `arguments` is the arglist of the resulting function.
 
-    (GEN EXPRESSION)
+  Examples:
+
+    (define-string color "white" "gray" "black")
+
+    (define-string (animal :distribution :weighted)
+      (100 (color "cat"))
+      (100 (color "dog"))
+      (100 (color "dragon")))
+
+  See the full documentation for more information.
+
+  
+
+### `GENERATE` (macro)
+
+    (GENERATE EXPRESSION)
 
 Generate a single Chancery expression.
 
-### `GEN-STRING` (macro)
+  Example:
+
+    (define-rule x 1 2 3)
+
+    (generate (x x x))
+    ; => (1 3 1)
+
+  
+
+### `GENERATE-STRING` (macro)
 
-    (GEN-STRING EXPRESSION)
+    (GENERATE-STRING EXPRESSION)
+
+Generate and stringify a single Chancery string expression.
+
+  Example:
 
-Generate a single Chancery string expression.
+    (define-string x 1 2 3)
+
+    (generate-string (x x x))
+    ; => "1 3 1"
+
+  
 
 ### `POS` (function)
 
--- a/docs/index.markdown	Thu Nov 02 22:32:50 2017 -0400
+++ b/docs/index.markdown	Fri Nov 03 01:05:31 2017 -0400
@@ -3,7 +3,7 @@
 
 [Tracery]: http://tracery.io/
 
-* **License:** MIT
+* **License:** MIT/X11
 * **Documentation:** <https://sjl.bitbucket.io/chancery/>
 * **Mercurial:** <http://bitbucket.org/sjl/chancery/>
 * **Git:** <http://github.com/sjl/chancery/>
--- a/examples/fantasy-weapons.lisp	Thu Nov 02 22:32:50 2017 -0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,41 +0,0 @@
-(in-package :chancery)
-(named-readtables:in-readtable :chancery)
-
-(define-rule (material :distribution (:zipf :exponent 0.7))
-  :iron
-  :steel
-  :silver
-  :mithril
-  :meteoric-iron
-  :adamantine)
-
-(define-rule kind
-  :dagger
-  :short-sword
-  :long-sword
-  :axe
-  :mace
-  :hammer)
-
-(define-rule (bonus :distribution :zipf)
-  1 2 3 4)
-
-(define-rule (monster :distribution :weighted)
-  (10 :goblin)
-  (5 :elf)
-  (5 :dwarf)
-  (1 :dragon))
-
-(define-rule magic
-  (('+ bonus) material kind)
-  (material kind :of monster :slaying)
-  (:glowing material kind))
-
-(define-rule vanilla
-  (material kind))
-
-(define-rule (weapon :distribution :weighted)
-  (10 vanilla)
-  (2 magic))
-
-(map nil #'print (gimme 40 (weapon)))
--- a/examples/git-commands.js	Thu Nov 02 22:32:50 2017 -0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,292 +0,0 @@
-{
-    "noun": [
-        "binary blob",
-        "packfile",
-        "refspec",
-        "blob",
-        "branch",
-        "changeset",
-        "commit",
-        "conflicted merge",
-        "current HEAD",
-        "file",
-        "head",
-        "merge",
-        "remote",
-        "object",
-        "patch",
-        "ref",
-        "repository",
-        "symlink",
-        "tag",
-        "tip"
-    ],
-    "shell-noun": [
-        "binary-blob",
-        "packfile",
-        "refspec",
-        "blob",
-        "branch",
-        "changeset",
-        "commit",
-        "conflicted-merge",
-        "current-head",
-        "file",
-        "head",
-        "merge",
-        "remote",
-        "object",
-        "patch",
-        "ref",
-        "repository",
-        "symlink",
-        "tag",
-        "tip"
-    ],
-    "shell-noun-cap": [
-        "BINARY-BLOB",
-        "PACKFILE",
-        "REFSPEC",
-        "BLOB",
-        "BRANCH",
-        "CHANGESET",
-        "COMMIT",
-        "CONFLICTED-MERGE",
-        "CURRENT-HEAD",
-        "FILE",
-        "HEAD",
-        "MERGE",
-        "REMOTE",
-        "OBJECT",
-        "PATCH",
-        "REF",
-        "REPOSITORY",
-        "SYMLINK",
-        "TAG",
-        "TIP"
-    ],
-    "git-location_": [
-        "repository",
-        "index",
-        "working tree",
-        "content-addressable filesystem",
-        "object store",
-        "reflog",
-        "current directory",
-        "current repository",
-        "current branch",
-        "checked-out branch",
-        "upstream repository",
-        "DAG"
-    ],
-    "git-folder_": [
-        "refs",
-        "logs",
-        "objects",
-        "hooks",
-        "HEAD",
-        "COMMIT_EDITMSG"
-    ],
-    "git-folder": [
-        ".git/#git-folder_#"
-    ],
-    "git-location": [
-        "the #git-location_#",
-        "#git-folder#"
-    ],
-    "external-location": [
-        "Hacker News",
-        "Stack Overflow",
-        "Twitter",
-        "Reddit",
-        "Github",
-        "Gitlab",
-        "Github's status page",
-        "/dev/random",
-        "/dev/urandom",
-        "your .gitconfig",
-        "the git man pages",
-        "the git source code",
-        "your home directory"
-    ],
-    "location": [
-        "#git-location#",
-        "#external-location#"
-    ],
-    "action": [
-        "[command:bisect][commanding:bisecting]",
-        "[command:clone][commanding:cloning]",
-        "[command:commit][commanding:committing]",
-        "[command:delete][commanding:deleting]",
-        "[command:display][commanding:displaying]",
-        "[command:fast-forward][commanding:fast-forwarding]",
-        "[command:fetch][commanding:fetching]",
-        "[command:merge][commanding:merging]",
-        "[command:move][commanding:moving]",
-        "[command:print][commanding:printing]",
-        "[command:prune][commanding:pruning]",
-        "[command:pull][commanding:pulling]",
-        "[command:push][commanding:pushing]",
-        "[command:record][commanding:recording]",
-        "[command:revert][commanding:reverting]",
-        "[command:remove][commanding:removing]",
-        "[command:rename][commanding:renaming]",
-        "[command:reset][commanding:resetting]",
-        "[command:resolve][commanding:resolving]",
-        "[command:show][commanding:showing]",
-        "[command:sign][commanding:signing]",
-        "[command:simplify][commanding:simplifying]",
-        "[command:update][commanding:updating]",
-        "[command:verify][commanding:verifying]"
-    ],
-    "action-verb": [
-        "bisecting",
-        "cloning",
-        "committing",
-        "deleting",
-        "displaying",
-        "fast-forwarding",
-        "fetching",
-        "merging",
-        "moving",
-        "printing",
-        "pruning",
-        "pulling",
-        "pushing",
-        "recording",
-        "reverting",
-        "removing",
-        "renaming",
-        "resetting",
-        "resolving",
-        "showing",
-        "signing",
-        "simplifying",
-        "updating",
-        "verifying"
-    ],
-    "refresh": [
-        "update",
-        "reset"
-    ],
-    "refreshing": [
-        "updating",
-        "resetting"
-    ],
-    "extremum": [
-        "newest",
-        "oldest",
-        "largest",
-        "smallest",
-        "sparsest",
-        "first",
-        "last",
-        "worst",
-        "simplest",
-        "best"
-    ],
-    "adjective": [
-        "merged",
-        "unmerged",
-        "symbolic",
-        "uncommitted",
-        "signed",
-        "unsigned",
-        "big-endian",
-        "little-endian",
-        "childless",
-        "binary"
-    ],
-    "age": [
-        "newest",
-        "oldest",
-        "first",
-        "last"
-    ],
-    "look-for": [
-        "search",
-        "grep",
-        "bisect",
-        "filter"
-    ],
-    "temporal-adverb": [
-        "before",
-        "after",
-        "without"
-    ],
-    "letter": [
-        "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k",
-        "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v",
-        "w", "x", "y", "z"
-    ],
-    "short-option_": [
-        "-#letter#",
-        "-#letter# #shell-noun-cap#"
-    ],
-    "long-option-with-noun": [
-        "--#noun#=<#noun#>"
-    ],
-    "set-fucking-noun": [
-        "[noun:#shell-noun#]"
-    ],
-    "long-option_": [
-        "#[#set-fucking-noun#]long-option-with-noun#",
-        "--#action-verb#",
-        "--#extremum#",
-        "--only-#adjective#",
-        "--only-#shell-noun.s#",
-        "--#action-verb#=<#shell-noun#>"
-    ],
-    "short-option": [
-        "#short-option_#",
-        "\\[#short-option_#\\]"
-    ],
-    "long-option": [
-        "#long-option_#",
-        "\\[#long-option_#\\]"
-    ],
-    "short-options": [
-        "#short-option#",
-        "#short-option# #short-option#"
-    ],
-    "options": [
-        "#long-option#",
-        "#short-options#",
-        "#short-options# #long-option#",
-        "#long-option# #short-options#"
-    ],
-    "number": [
-        "200", "1733", "1925", "1235", "1478", "1770", "365", "269", "1436",
-        "1552", "1867", "777", "452", "941", "770", "684", "1405", "1828",
-        "1982", "705", "994", "1452", "239", "1619", "1303", "260", "137",
-        "1953", "1123", "1697", "246", "1281", "1243", "1970", "668", "288",
-        "1387", "1115", "841", "1076", "337", "1539", "273", "888", "1329",
-        "830", "1739", "753", "1519", "814", "106", "817", "1562", "1047", "69",
-        "869", "1475", "955", "1699", "1128", "1891", "1425", "571", "654",
-        "1645", "737", "1353", "325", "1465", "1240", "1102", "1001", "1821",
-        "4", "1996", "1016", "921", "920", "384", "1440", "1768", "707", "1372",
-        "1053", "1148", "1173", "1412", "261", "100", "213", "1808", "1937",
-        "168", "959", "393", "1781", "598", "1602", "454", "1781", "808", "1663"
-    ],
-    "description": [
-        "#look-for# #location# for the #age# #noun# and #command# it",
-        "read #number# bytes from #location# and #command# them",
-        "#command# the #extremum# #noun# in #git-location#",
-        "#command# #noun.a# #temporal-adverb# #refreshing# #git-location#",
-        "#command# and push all #adjective# #noun.s# to #location#",
-        "#command# all #adjective# #noun.s# in #git-location#",
-        "#command# the #extremum# #noun# and merge it into #git-location#",
-        "#command# some #noun.s# from a remote",
-        "#command# two or more #noun.s# and save them to #location#",
-        "move or #command# #noun.a# in #git-location#",
-        "rebase #noun.a# onto #location# after #commanding# it",
-        "#command# and #refresh# #git-location#",
-        "list, #command#, or delete #noun.s#"
-    ],
-    "origin_": [
-        "git #command# #options#\n#description.capitalize#"
-    ],
-    "origin": [
-        "#[#action#]origin_#"
-    ]
-}
--- a/examples/git-commands.lisp	Thu Nov 02 22:32:50 2017 -0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,222 +0,0 @@
-(in-package :chancery)
-(named-readtables:in-readtable :chancery)
-
-(define-string noun
-  "binary blob"
-  "packfile"
-  "refspec"
-  "blob"
-  "branch"
-  "changeset"
-  "commit"
-  "conflicted merge"
-  "current HEAD"
-  "file"
-  "head"
-  "merge"
-  "remote"
-  "object"
-  "patch"
-  "ref"
-  "repository"
-  "symlink"
-  "tag"
-  "tip")
-
-
-(define-string git-location%
-  "repository"
-  "index"
-  "working tree"
-  "content-addressable filesystem"
-  "object store"
-  "reflog"
-  "current directory"
-  "current repository"
-  "current branch"
-  "checked-out branch"
-  "upstream repository"
-  "DAG")
-
-(define-string git-folder%
-  ""
-  "refs"
-  "logs"
-  "objects"
-  "hooks"
-  "HEAD"
-  "COMMIT_EDITMSG")
-
-(define-string git-folder
-  (".git/" :. git-folder%))
-
-(define-string git-location
-  ("the" git-location%)
-  git-folder)
-
-(define-string external-location
-  "Hacker News"
-  "Stack Overflow"
-  "Twitter"
-  "Reddit"
-  "Github"
-  "Gitlab"
-  "Github's status page"
-  "/dev/random"
-  "/dev/urandom"
-  "your .gitconfig"
-  "the git man pages"
-  "the git source code"
-  "the blockchain"
-  "your home directory")
-
-(define-string location
-  git-location
-  external-location)
-
-
-(define-rule action
-  ("bisect" "bisecting")
-  ("clone" "cloning")
-  ("commit" "committing")
-  ("delete" "deleting")
-  ("display" "displaying")
-  ("fast-forward" "fast-forwarding")
-  ("fetch" "fetching")
-  ("merge" "merging")
-  ("move" "moving")
-  ("print" "printing")
-  ("prune" "pruning")
-  ("pull" "pulling")
-  ("push" "pushing")
-  ("record" "recording")
-  ("revert" "reverting")
-  ("remove" "removing")
-  ("rename" "renaming")
-  ("reset" "resetting")
-  ("resolve" "resolving")
-  ("show" "showing")
-  ("sign" "signing")
-  ("simplify" "simplifying")
-  ("update" "updating")
-  ("verify" "verifying"))
-
-(defun action-verb ()
-  (first (action)))
-
-
-(define-string refresh
-  "update"
-  "reset")
-
-(define-string refreshing
-  "updating"
-  "resetting")
-
-
-(define-string extremum
-  "newest"
-  "oldest"
-  "largest"
-  "smallest"
-  "sparsest"
-  "first"
-  "last"
-  "worst"
-  "simplest"
-  "best")
-
-(define-string adjective
-  "merged"
-  "unmerged"
-  "symbolic"
-  "uncommitted"
-  "signed"
-  "unsigned"
-  "big-endian"
-  "little-endian"
-  "childless"
-  "binary")
-
-
-(define-string age
-  "newest"
-  "oldest"
-  "first"
-  "last")
-
-(define-string look-for
-  "search"
-  "grep"
-  "bisect"
-  "filter")
-
-(define-string temporal-adverb
-  "before"
-  "after"
-  "without")
-
-
-(defun letter ()
-  (aref "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
-        (random 52)))
-
-(defun shellify (str)
-  (string-downcase (substitute #\- #\space str)))
-
-(define-string short-option%
-  ("-" :. letter)
-  ("-" :. letter [noun shellify string-upcase]))
-
-(define-string long-option%
-  !(let ((noun $[noun shellify]))
-     $("--" :. !noun :. "=<" :. !noun :. ">"))
-  ("--" :. action-verb)
-  ("--" :. extremum)
-  ("--only-" :. adjective)
-  ("--only-" :. [noun shellify s])
-  ("--" :. action-verb :. "=<" :. [noun shellify] :. ">"))
-
-(define-string short-option
-  short-option%
-  ("[" :. short-option% :. "]"))
-
-(define-string long-option
-  long-option%
-  ("[" :. long-option% :. "]"))
-
-(define-string short-options
-  short-option
-  (short-option short-option))
-
-(define-string options
-  long-option
-  short-options
-  (short-options long-option)
-  (long-option short-options))
-
-
-(defparameter *command* nil)
-(defparameter *commanding* nil)
-
-(define-string (description :arguments (command commanding))
-  (look-for location "for the" age noun "and" !command "it")
-  ("read" !(+ 2 (random 2000)) "bytes from" location "and" !command "them")
-  (!command "the" extremum noun "in" git-location)
-  (!command [noun a] temporal-adverb refreshing git-location)
-  (!command "and push all" adjective [noun s] "to" location)
-  (!command "all" adjective [noun s] "in" git-location)
-  (!command "the" extremum "and merge it into" git-location)
-  (!command "some" [noun s] "from a remote")
-  (!command "two or more" [noun s] "and save them to" location)
-  ("move or" !command [noun a] "in" git-location)
-  ("rebase" [noun a] "onto" location "after" !commanding "it")
-  (!command "and" refresh git-location)
-  ("list," !command :. ", or delete" [noun s]))
-
-(defun entry ()
-  (destructuring-bind (command commanding) (action)
-    $("git" !command options #\newline :.
-      [!(description command commanding) cap])))
-
-
--- a/examples/story.lisp	Thu Nov 02 22:32:50 2017 -0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,49 +0,0 @@
-(in-package :chancery)
-(named-readtables:in-readtable :chancery)
-
-(defun pronoun (gender)
-  (case gender
-    (:female "she")
-    (:male "he")
-    (:neuter "it")
-    (t "they")))
-
-(defun posessive-pronoun (gender)
-  (case gender
-    (:female "her")
-    (:male "his")
-    (:neuter "its")
-    (t "their")))
-
-
-(defparameter *name* nil)
-(defparameter *pronoun* nil)
-(defparameter *possessive* nil)
-
-(define-rule hero
-  (list "freyja" :female)
-  (list "loki" :male)
-  (list "time" :neuter)
-  (list "frobboz" :frobbian))
-
-(define-rule weapon
-  "axe" "sword" "banana")
-
-(define-rule animal
-  "mouse" "squirrel" "beaver" "antelope" "rabbit" "elk")
-
-(define-rule monster
-  "dragon" "kraken" "chimera")
-
-(define-rule story%
-  ([*name* cap] "took up" *possessive* "mighty" weapon "and went forth."
-   [*pronoun* cap] "slew the" monster "and the people rejoiced.")
-  ("Once upon a time there was" [animal a] "named" [*name* cap] :. "."
-   [*pronoun* cap] "went to the village and was very happy."))
-
-(defun story ()
-  (destructuring-bind (name gender) (hero)
-    (let ((*name* name)
-          (*pronoun* (pronoun gender))
-          (*possessive* (posessive-pronoun gender)))
-      (story%))))
--- a/src/chancery.lisp	Thu Nov 02 22:32:50 2017 -0400
+++ b/src/chancery.lisp	Fri Nov 03 01:05:31 2017 -0400
@@ -64,7 +64,8 @@
 
 
 ;;;; RNG ----------------------------------------------------------------------
-(defparameter *random* #'random)
+(defparameter *random* #'random
+  "The random number generation function to use (default: `CL:RANDOM`).")
 
 (defun chancery-random (n)
   (funcall *random* n))
@@ -221,16 +222,99 @@
 
 
 (defmacro define-rule (name-and-options &rest expressions)
+  "Define a function that will return random elements of `expressions`.
+
+  `name-and-options` should be of the form:
+
+    (name &key documentation (distribution :uniform) (arguments '()))
+
+  If no options are needed a bare symbol can be given.
+
+  `name` is the symbol under which the resulting function will be defined.
+
+  `documentation` will be used as a docstring for the resulting function.
+
+  `distribution` denotes the distribution of elements returned.
+
+  `arguments` is the arglist of the resulting function.
+
+  Examples:
+
+    (define-rule color
+      :blue
+      :green
+      :red)
+
+    (define-rule (metal :documentation \"Return a random metal.\"
+                        :distribution :zipf)
+      :copper
+      :silver
+      :gold
+      :platinum)
+
+  See the full documentation for more information.
+
+  "
   (compile-define-rule #'compile-expression name-and-options expressions))
 
 (defun create-rule (expressions &rest options)
+  "Return a function that will return random elements of `expressions`.
+
+  `options` should be of the form:
+
+    (&key documentation (distribution :uniform) (arguments '()))
+
+  `documentation` will be used as a docstring for the resulting function.
+
+  `distribution` denotes the distribution of elements returned.
+
+  `arguments` is the arglist of the resulting function.
+
+  Examples:
+
+    (create-rule (list :blue :red :green))
+
+    (create-rule (list :copper :silver :gold :platinum)
+      :documentation \"Return a random metal.\"
+      :distribution :zipf)
+
+  See the full documentation for more information.
+
+  "
   (compile-create-rule #'compile-expression options expressions))
 
+(defmacro generate (expression)
+  "Generate a single Chancery expression.
 
-(defmacro generate (expression)
-  "Generate a single Chancery expression."
+  Example:
+
+    (define-rule x 1 2 3)
+
+    (generate (x x x))
+    ; => (1 3 1)
+
+  "
   (compile-expression expression))
 
+(defun invoke-generate (expression)
+  "Generate a single Chancery expression.
+
+  THIS FUNCTION IS EXPERIMENTAL AND SUBJECT TO CHANGE IN THE FUTURE.
+
+  Because this is a function, not a macro, you'll need to do the quoting
+  yourself:
+
+    (define-rule x 1 2 3)
+
+    (generate (x x x))
+    ; => (1 3 3)
+
+    (invoke-generate '(x x x))
+    ; => (2 1 2)
+
+  "
+  (eval (compile-expression expression)))
+
 
 ;;;; Strings ------------------------------------------------------------------
 (defun compile-string-combination (list)
@@ -240,9 +324,13 @@
 
 (defun compile-string-modifiers (vector)
   ; #("foo" a b c) => (c (b (a "foo")))
-  (reduce (flip #'list) vector
-          :start 1
-          :initial-value (compile-expression (aref vector 0))))
+  `(princ-to-string
+     ,(reduce (flip #'list) vector
+              :start 1
+              :initial-value (compile-string-expression (aref vector 0)))))
+
+(defun compile-string-other (expr)
+  `(princ-to-string ,expr))
 
 (defun compile-string-expression (expression)
   (typecase expression
@@ -252,20 +340,99 @@
     (special-form (compile-special-form expression))
     (vector (compile-string-modifiers expression))
     (cons (compile-string-combination expression))
-    (t expression)))
+    (t (compile-string-other expression))))
 
 
 (defmacro define-string (name-and-options &rest expressions)
+  "Define a function that will return random stringified elements of `expressions`.
+
+  `name-and-options` should be of the form:
+
+    (name &key documentation (distribution :uniform) (arguments '()))
+
+  If no options are needed a bare symbol can be given.
+
+  `name` is the symbol under which the resulting function will be defined.
+
+  `documentation` will be used as a docstring for the resulting function.
+
+  `distribution` denotes the distribution of elements returned.
+
+  `arguments` is the arglist of the resulting function.
+
+  Examples:
+
+    (define-string color \"white\" \"gray\" \"black\")
+
+    (define-string (animal :distribution :weighted)
+      (100 (color \"cat\"))
+      (100 (color \"dog\"))
+      (100 (color \"dragon\")))
+
+  See the full documentation for more information.
+
+  "
   (compile-define-rule #'compile-string-expression name-and-options expressions))
 
 (defun create-string (expressions &rest options)
+  "Return a function that will return random stringified elements of `expressions`.
+
+  `options` should be of the form:
+
+    (&key documentation (distribution :uniform) (arguments '()))
+
+  `documentation` will be used as a docstring for the resulting function.
+
+  `distribution` denotes the distribution of elements returned.
+
+  `arguments` is the arglist of the resulting function.
+
+  Examples:
+
+    (create-string (list \"white\" \"gray\" \"black\"))
+
+    (create-string '((100 (color \"cat\"))
+                     (100 (color \"dog\"))
+                     (100 (color \"dragon\")))
+      :distribution :weighted)
+
+  See the full documentation for more information.
+
+  "
   (compile-create-rule #'compile-string-expression options expressions))
 
+(defmacro generate-string (expression)
+  "Generate and stringify a single Chancery string expression.
 
-(defmacro generate-string (expression)
-  "Generate a single Chancery string expression."
+  Example:
+
+    (define-string x 1 2 3)
+
+    (generate-string (x x x))
+    ; => \"1 3 1\"
+
+  "
   (compile-string-expression expression))
 
+(defun invoke-generate-string (expression)
+  "Generate and stringify a single Chancery expression.
+
+  THIS FUNCTION IS EXPERIMENTAL AND SUBJECT TO CHANGE IN THE FUTURE.
+
+  Because this is a function, not a macro, you'll need to do the quoting
+  yourself:
+
+    (define-string x 1 2 3)
+
+    (generate-string (x x x))
+    ; => \"1 3 3\"
+
+    (invoke-generate-string '(x x x))
+    ; => \"2 1 2\"
+
+  "
+  (eval (compile-string-expression expression)))
+
 
 ;;;; Modifiers ----------------------------------------------------------------
 (defun cap (string)