# HG changeset patch # User Steve Losh # Date 1513637140 18000 # Node ID 93a99efafe6fd36098bec142c600ce6cc2114512 # Parent abff4fe021789ea5585f7aec2fd0ce2b310a9398 Update docs. Project is in Quicklisp now diff -r abff4fe02178 -r 93a99efafe6f docs/01-installation.markdown --- a/docs/01-installation.markdown Fri Nov 03 23:48:31 2017 -0400 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,8 +0,0 @@ -Installation -============ - -Chancery is compatible with Quicklisp, but not *in* Quicklisp (yet?). You can -clone the repository into your [Quicklisp local-projects][local] directory for -now. - -[local]: https://www.quicklisp.org/beta/faq.html#local-project diff -r abff4fe02178 -r 93a99efafe6f docs/01-usage.markdown --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/docs/01-usage.markdown Mon Dec 18 17:45:40 2017 -0500 @@ -0,0 +1,433 @@ +Usage +===== + +Chancery is a library for procedurally generating text and data in Common +Lisp. It's heavily inspired by [Tracery][], and is essentially just some Lispy +syntactic sugar for writing [grammars][cfg]. + +[Tracery]: http://www.crystalcodepalace.com/traceryTut.html +[cfg]: https://en.wikipedia.org/wiki/Context-free_grammar + +[TOC] + +Rules +----- + +Rules are the core construct Chancery uses to generate data, and can be created +with `define-rule`: + + :::lisp + (define-rule animal + "cat" + "dog" + "mouse") + + (define-rule color + "black" + "white" + "brown" + "gray") + +Rules are compiled into vanilla Lisp functions, so you can call them like you +would any other function. + + :::lisp + (loop :repeat 10 + :collect (list (color) (animal))) + ; => + (("gray" "dog") ("white" "mouse") ("gray" "dog") + ("black" "mouse") ("gray" "cat") ("gray" "cat") + ("white" "mouse") ("white" "dog") ("gray" "mouse") + ("brown" "cat")) + +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: + + :::lisp + (define-rule cat-name + "fluffy" + "whiskers") + + (define-rule dog-name + "spot" + "fido" + "lassie") + + (define-rule animal-name + cat-name + dog-name) + + (loop :repeat 10 :collect (animal-name)) + ; => + ("spot" "spot" "fido" "fluffy" "fido" "spot" + "fluffy" "spot" "spot" "lassie") + +Lists recursively evaluate their members and return the result as a fresh list: + + :::lisp + (define-rule pet + (cat-name color :cat) + (dog-name color :dog)) + + (loop :repeat 5 :collect (pet)) + ; => + (("fluffy" "brown" :CAT) ("fluffy" "white" :CAT) + ("fido" "brown" :DOG) ("lassie" "white" :DOG) + ("fluffy" "black" :CAT)) + +If you want to return a literal symbol or list from a rule you can `quote` it: + + :::lisp + (define-rule foo + 'a + '(x y)) + + (loop :repeat 5 :collect (foo)) + ; => + (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 +------------- + +By default each body term in a rule has an equal chance of being chosen. + +Chancery also includes support for weighting the terms so some will be chosen +more often than others: + + :::lisp + (define-rule (metal :distribution :weighted) + (10 :iron) + (5 :steel) + (2 :silver) + (1 :gold) + (1 :platinum)) + +Weighting each term by hand can be tedious. Chancery can automatically +calculate weights based on the order of the body terms according to [Zipf's +law][zipf]: + + :::lisp + (define-rule (armor-type :distribution :zipf) + :scale-mail + :chain-mail + :banded-mail + :plate-mail) + +The Zipf distribution can 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 + +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") + + (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. diff -r abff4fe02178 -r 93a99efafe6f docs/02-reference.markdown --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/docs/02-reference.markdown Mon Dec 18 17:45:40 2017 -0500 @@ -0,0 +1,248 @@ +# API Reference + +The following is a list of all user-facing parts of Chancery. + +If there are backwards-incompatible changes to anything listed here, they will +be noted in the changelog and the author will feel bad. + +Anything not listed here is subject to change at any time with no warning, so +don't touch it. + +[TOC] + +## Package `CHANCERY` + +### `*RANDOM*` (variable) + +The random number generation function to use (default: `CL:RANDOM`). + +### `A` (function) + + (A STRING) + +Add an indefinite article (a or an) to the front of `string`. + +### `CAP` (function) + + (CAP STRING) + +Capitalize the first character of `string`. + +### `CAP-ALL` (function) + + (CAP-ALL STRING) + +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) + +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. + + + +### `GENERATE` (macro) + + (GENERATE EXPRESSION) + +Generate a single Chancery expression. + + Example: + + (define-rule x 1 2 3) + + (generate (x x x)) + ; => (1 3 1) + + + +### `GENERATE-STRING` (macro) + + (GENERATE-STRING EXPRESSION) + +Generate and stringify a single Chancery string expression. + + Example: + + (define-string x 1 2 3) + + (generate-string (x x x)) + ; => "1 3 1" + + + +### `INVOKE-GENERATE` (function) + + (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) + + + +### `INVOKE-GENERATE-STRING` (function) + + (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" + + + +### `POS` (function) + + (POS STRING) + +Make `string` posessive by adding an apostrophe (and possibly an s). + +### `S` (function) + + (S STRING) + +Pluralize `string`. + diff -r abff4fe02178 -r 93a99efafe6f docs/02-usage.markdown --- a/docs/02-usage.markdown Fri Nov 03 23:48:31 2017 -0400 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,433 +0,0 @@ -Usage -===== - -Chancery is a library for procedurally generating text and data in Common -Lisp. It's heavily inspired by [Tracery][], and is essentially just some Lispy -syntactic sugar for writing [grammars][cfg]. - -[Tracery]: http://www.crystalcodepalace.com/traceryTut.html -[cfg]: https://en.wikipedia.org/wiki/Context-free_grammar - -[TOC] - -Rules ------ - -Rules are the core construct Chancery uses to generate data, and can be created -with `define-rule`: - - :::lisp - (define-rule animal - "cat" - "dog" - "mouse") - - (define-rule color - "black" - "white" - "brown" - "gray") - -Rules are compiled into vanilla Lisp functions, so you can call them like you -would any other function. - - :::lisp - (loop :repeat 10 - :collect (list (color) (animal))) - ; => - (("gray" "dog") ("white" "mouse") ("gray" "dog") - ("black" "mouse") ("gray" "cat") ("gray" "cat") - ("white" "mouse") ("white" "dog") ("gray" "mouse") - ("brown" "cat")) - -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: - - :::lisp - (define-rule cat-name - "fluffy" - "whiskers") - - (define-rule dog-name - "spot" - "fido" - "lassie") - - (define-rule animal-name - cat-name - dog-name) - - (loop :repeat 10 :collect (animal-name)) - ; => - ("spot" "spot" "fido" "fluffy" "fido" "spot" - "fluffy" "spot" "spot" "lassie") - -Lists recursively evaluate their members and return the result as a fresh list: - - :::lisp - (define-rule pet - (cat-name color :cat) - (dog-name color :dog)) - - (loop :repeat 5 :collect (pet)) - ; => - (("fluffy" "brown" :CAT) ("fluffy" "white" :CAT) - ("fido" "brown" :DOG) ("lassie" "white" :DOG) - ("fluffy" "black" :CAT)) - -If you want to return a literal symbol or list from a rule you can `quote` it: - - :::lisp - (define-rule foo - 'a - '(x y)) - - (loop :repeat 5 :collect (foo)) - ; => - (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 -------------- - -By default each body term in a rule has an equal chance of being chosen. - -Chancery also includes support for weighting the terms so some will be chosen -more often than others: - - :::lisp - (define-rule (metal :distribution :weighted) - (10 :iron) - (5 :steel) - (2 :silver) - (1 :gold) - (1 :platinum)) - -Weighting each term by hand can be tedious. Chancery can automatically -calculate weights based on the order of the body terms according to [Zipf's -law][zipf]: - - :::lisp - (define-rule (armor-type :distribution :zipf) - :scale-mail - :chain-mail - :banded-mail - :plate-mail) - -The Zipf distribution can 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 - -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") - - (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. diff -r abff4fe02178 -r 93a99efafe6f docs/03-changelog.markdown --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/docs/03-changelog.markdown Mon Dec 18 17:45:40 2017 -0500 @@ -0,0 +1,11 @@ +Changelog +========= + +Here's the list of changes in each released version. + +[TOC] + +v1.0.0 +------ + +Initial version. diff -r abff4fe02178 -r 93a99efafe6f docs/03-reference.markdown --- a/docs/03-reference.markdown Fri Nov 03 23:48:31 2017 -0400 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,248 +0,0 @@ -# API Reference - -The following is a list of all user-facing parts of Chancery. - -If there are backwards-incompatible changes to anything listed here, they will -be noted in the changelog and the author will feel bad. - -Anything not listed here is subject to change at any time with no warning, so -don't touch it. - -[TOC] - -## Package `CHANCERY` - -### `*RANDOM*` (variable) - -The random number generation function to use (default: `CL:RANDOM`). - -### `A` (function) - - (A STRING) - -Add an indefinite article (a or an) to the front of `string`. - -### `CAP` (function) - - (CAP STRING) - -Capitalize the first character of `string`. - -### `CAP-ALL` (function) - - (CAP-ALL STRING) - -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) - -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. - - - -### `GENERATE` (macro) - - (GENERATE EXPRESSION) - -Generate a single Chancery expression. - - Example: - - (define-rule x 1 2 3) - - (generate (x x x)) - ; => (1 3 1) - - - -### `GENERATE-STRING` (macro) - - (GENERATE-STRING EXPRESSION) - -Generate and stringify a single Chancery string expression. - - Example: - - (define-string x 1 2 3) - - (generate-string (x x x)) - ; => "1 3 1" - - - -### `INVOKE-GENERATE` (function) - - (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) - - - -### `INVOKE-GENERATE-STRING` (function) - - (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" - - - -### `POS` (function) - - (POS STRING) - -Make `string` posessive by adding an apostrophe (and possibly an s). - -### `S` (function) - - (S STRING) - -Pluralize `string`. - diff -r abff4fe02178 -r 93a99efafe6f docs/04-changelog.markdown --- a/docs/04-changelog.markdown Fri Nov 03 23:48:31 2017 -0400 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,11 +0,0 @@ -Changelog -========= - -Here's the list of changes in each released version. - -[TOC] - -v1.0.0 ------- - -Initial version. diff -r abff4fe02178 -r 93a99efafe6f docs/api.lisp --- a/docs/api.lisp Fri Nov 03 23:48:31 2017 -0400 +++ b/docs/api.lisp Mon Dec 18 17:45:40 2017 -0500 @@ -13,7 +13,7 @@ (d-api:generate-documentation :chancery - #p"docs/03-reference.markdown" + #p"docs/02-reference.markdown" (list "CHANCERY") *header* :title "API Reference") diff -r abff4fe02178 -r 93a99efafe6f docs/index.markdown --- a/docs/index.markdown Fri Nov 03 23:48:31 2017 -0400 +++ b/docs/index.markdown Mon Dec 18 17:45:40 2017 -0500 @@ -1,7 +1,7 @@ Chancery is a library for procedurally generating text and data in Common Lisp. It's heavily inspired by [Tracery][]. -[Tracery]: http://tracery.io/ +Chancery can be installed with [Quicklisp][]: `(ql:quickload :chancery)` * **License:** MIT/X11 * **Documentation:** @@ -12,3 +12,6 @@ *terrible*, but is not a high priority. It is currently not thread-safe, but this may happen in the future. + +[Tracery]: http://tracery.io/ +[quicklisp]: https://quicklisp.org/