# HG changeset patch # User Steve Losh # Date 1509685531 14400 # Node ID 53d4a4db967fd9e90c4dd326a984d89d93869447 # Parent ca108d2f3b7947f2f557dc9798eb0f0dbc6bdbb9 Finish the damn docs diff -r ca108d2f3b79 -r 53d4a4db967f docs/02-usage.markdown --- 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. diff -r ca108d2f3b79 -r 53d4a4db967f docs/03-reference.markdown --- 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) diff -r ca108d2f3b79 -r 53d4a4db967f docs/index.markdown --- 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:** * **Mercurial:** * **Git:** diff -r ca108d2f3b79 -r 53d4a4db967f examples/fantasy-weapons.lisp --- 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))) diff -r ca108d2f3b79 -r 53d4a4db967f examples/git-commands.js --- 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_#" - ] -} diff -r ca108d2f3b79 -r 53d4a4db967f examples/git-commands.lisp --- 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]))) - - diff -r ca108d2f3b79 -r 53d4a4db967f examples/story.lisp --- 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%)))) diff -r ca108d2f3b79 -r 53d4a4db967f src/chancery.lisp --- 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)