madlang 2.1.0.4 → 2.1.1.0
raw patch · 10 files changed
+179/−131 lines, 10 filesdep +template-haskelldep ~ansi-wl-pprintdep ~basedep ~composition
Dependencies added: template-haskell
Dependency ranges changed: ansi-wl-pprint, base, composition, containers, criterion, directory, hspec, hspec-megaparsec, madlang, megaparsec, microlens, mtl, mwc-random, optparse-applicative, text, tibetan-utils
Files
- README.md +30/−21
- madlang.cabal +104/−94
- src/Text/Madlibs/Ana/Parse.hs +21/−5
- src/Text/Madlibs/Ana/ParseUtils.hs +2/−2
- src/Text/Madlibs/Internal/Types.hs +6/−2
- src/Text/Madlibs/Internal/Utils.hs +1/−1
- test/Spec.hs +5/−3
- test/templates/gambling.mad +2/−2
- test/templates/include.mad +1/−1
- test/templates/modifiers.mad +7/−0
README.md view
@@ -1,18 +1,21 @@ # Madlang DSL for generating random text [](https://travis-ci.org/vmchale/madlibs) -This is the Madlang DSL for generating random text. There is also a vim plugin, available [here](https://github.com/vmchale/madlang-vim). +This is the Madlang DSL for generating text. You specify a template, and Madlang+will create randomized text from the template. -It enables you to generate random templated text with very little effort or expertise. +It is an interpreted language, written in Haskell. It runs from the command line, but it is also a Haskell library that can be integrated into [other projects](https://github.com/vmchale/toboggan). -It can be used for twitter bots and provides human-readable syntax for Markov-chains often used in natural language processing. +There is also a vim plugin for syntax highlighting, available [here](https://github.com/vmchale/madlang-vim). -## Exmaples+It can be used for twitter bots (among other things) and provides human-readable syntax for the Markov+chains that are often used to generate text. -An exmaple is worth a thousand words, so suppose you wanted to generate a mediocre fortune telling bot. You could write the following code:+## Examples -```+An example is worth a thousand words, so suppose you wanted to generate a mediocre fortune telling bot. You could write the following code:++```madlang :define person 0.7 "A close friend will " 0.3 "You will "@@ -22,36 +25,45 @@ :define fortune 0.5 "drink a boatload of milk" 0.5 "get angry for no reason"+:define intense+ 1.0 person "wrestle in the WWE".to_upper+ 1.0 person "bite in a bottle of hot sauce".to_upper :return- 0.8 person fortune+ 0.7 person fortune+ 0.1 intense 0.2 goodfortune ``` -There are two "statements" in madlang, `:define` and `:return`. `:return` is the main string we'll be spitting back, so there can be only one per file. `:define` on the other hand can be used to make multiple templates. These templates are combinations of strings (enclosed in quotes) and names of other templates.+### Syntax +There are two keywords in madlang: `:define` and `:return`. `:return` is the main string we'll be spitting back, so there can be only one per file. `:define` on the other hand can be used to make multiple templates. These templates are combinations of strings (enclosed in quotes) and names of other templates.+ Of course, you can't have a circular reference with names - if `goodfortune` depends on `fortune` while `fortune` depends on `goodfortune`, we end up with either no fortune or an infinite fortune. So we throw an error. -## Using the libary+There is also a Shakespearean insult generator demo available in+`demo/shakespeare.mad` +## Using the library+ The main function you'll want to use is probably `runFile`; it reads a file and generates randomized text: ```- λ:> runFile [] "test/templates/gambling.mad"- "heads"+ λ:> runFile [] "demo/shakespeare.mad"+ "Thou hasty-witted gleeking puttock!" ``` -Haddock documentation of all available functionality is located [here](https://hackage.haskell.org/package/madlang#readme).+Haddock documentation of all library functionality is located [here](https://hackage.haskell.org/package/madlang#readme). ## Installation ### Releases -If you're on windows or linux, grabbing release binaries is probably the-easiest. Find them [here](https://github.com/vmchale/madlibs/releases).+If you're on windows or linux, grabbing release binaries simplest. +Find them [here](https://github.com/vmchale/madlibs/releases). ### Nix -If you're on linux or mac, you can just grab the binaries via nix.+If you're on linux or mac, you can get up-to-date binaries via nix. Download nix with @@ -69,17 +81,14 @@ curl -sSL http://haskellstack.org | sh ``` -Then run `stack install madlang --resolver nightly` and you'll get the `madlang` executable installed on your path.--You can also run `stack install` in the appropriate directory after cloning this-repository. +Then run `stack install madlang --resolver nightly` and you'll get the `madlang` executable installed on your path. This may take a bit of time, as it will build *all* dependencies of `madlang` first. ### Use To use it, try ```- $ madlang run fortune-teller.mad+ $ madlang run demo/shakespeare.mad ``` You can do `madlang --help` if you want a couple other options for debugging.
madlang.cabal view
@@ -1,102 +1,112 @@-name: madlang-version: 2.1.0.4-synopsis: Randomized templating language DSL-description: Please see README.md-homepage: https://github.com/vmchale/madlang#readme-license: BSD3-license-file: LICENSE-author: Vanessa McHale-maintainer: tmchale@wisc.edu-copyright: Copyright: (c) 2016 Vanessa McHale-category: Web-build-type: Simple-stability: experimental-extra-source-files: README.md- , test/templates/fortune-teller.mad- , test/templates/gambling.mad- , test/templates/var.mad- , test/templates/ordered.mad- , test/templates/include.mad- , test/templates/err/*.mad- , bench/templates/*.mad- , stack.yaml- , bash/mkCompletions-cabal-version: >=1.10+name: madlang+version: 2.1.1.0+cabal-version: >=1.10+build-type: Simple+license: BSD3+license-file: LICENSE+copyright: Copyright: (c) 2016 Vanessa McHale+maintainer: tmchale@wisc.edu+stability: experimental+homepage: https://github.com/vmchale/madlang#readme+synopsis: Randomized templating language DSL+description:+ Please see README.md+category: Web+author: Vanessa McHale+extra-source-files:+ README.md+ test/templates/fortune-teller.mad+ test/templates/gambling.mad+ test/templates/var.mad+ test/templates/ordered.mad+ test/templates/include.mad+ test/templates/modifiers.mad+ test/templates/err/*.mad+ bench/templates/*.mad+ stack.yaml+ bash/mkCompletions -Flag llvm-fast {- Description: Enable build with llvm backend- Default: False-}+source-repository head+ type: git+ location: https://github.com/vmchale/madlang +flag llvm-fast+ description:+ Enable build with llvm backend+ default: False+ library- hs-source-dirs: src- exposed-modules: Text.Madlibs- other-modules: Text.Madlibs.Ana.ParseUtils- , Text.Madlibs.Cata.Run- , Text.Madlibs.Ana.Parse- , Text.Madlibs.Ana.Resolve- , Text.Madlibs.Internal.Types- , Text.Madlibs.Internal.Utils- , Text.Madlibs.Cata.SemErr- , Text.Madlibs.Exec.Main- build-depends: base >= 4.7 && < 5- , megaparsec- , text- , optparse-applicative- , composition- , directory- , mwc-random- , microlens- , mtl- , ansi-wl-pprint- , containers- , tibetan-utils- default-language: Haskell2010- default-extensions: OverloadedStrings- , DeriveGeneric- , DeriveFunctor- , DeriveAnyClass+ exposed-modules:+ Text.Madlibs+ build-depends:+ base >=4.7 && <5,+ megaparsec >=5.2.0 && <5.3,+ text >=1.2.2.1 && <1.3,+ optparse-applicative >=0.13.2.0 && <0.14,+ composition >=1.0.2.1 && <1.1,+ directory >=1.3.0.0 && <1.4,+ mwc-random >=0.13.5.0 && <0.14,+ template-haskell >=2.11.1.0 && <2.12,+ microlens >=0.4.8.0 && <0.5,+ mtl >=2.2.1 && <2.3,+ ansi-wl-pprint >=0.6.7.3 && <0.7,+ containers >=0.5.7.1 && <0.6,+ tibetan-utils >=0.1.1.2 && <0.2+ default-language: Haskell2010+ default-extensions: OverloadedStrings DeriveGeneric DeriveFunctor+ DeriveAnyClass+ hs-source-dirs: src+ other-modules:+ Text.Madlibs.Ana.ParseUtils+ Text.Madlibs.Cata.Run+ Text.Madlibs.Ana.Parse+ Text.Madlibs.Ana.Resolve+ Text.Madlibs.Internal.Types+ Text.Madlibs.Internal.Utils+ Text.Madlibs.Cata.SemErr+ Text.Madlibs.Exec.Main executable madlang- hs-source-dirs: app- main-is: Main.hs- if flag(llvm-fast)- ghc-options: -threaded -rtsopts -with-rtsopts=-N -fllvm -optlo-O3 -O3- else- ghc-options: -threaded -rtsopts -with-rtsopts=-N - build-depends: base- , madlang- default-language: Haskell2010--benchmark tweeths-bench- type: exitcode-stdio-1.0- hs-source-dirs: bench- main-is: Bench.hs- build-depends: base- , criterion- , madlang- , megaparsec- , text- if flag(llvm-fast)- ghc-options: -threaded -rtsopts -with-rtsopts=-N -fllvm -optlo-O3 -O3- else- ghc-options: -threaded -rtsopts -with-rtsopts=-N -O3- default-language: Haskell2010+ + if flag(llvm-fast)+ ghc-options: -threaded -rtsopts -with-rtsopts=-N -fllvm -optlo-O3 -O3+ else+ ghc-options: -threaded -rtsopts -with-rtsopts=-N+ main-is: Main.hs+ build-depends:+ base >=4.9.1.0 && <4.10,+ madlang >=2.1.1.0 && <2.2+ default-language: Haskell2010+ hs-source-dirs: app test-suite madlang-test- type: exitcode-stdio-1.0- hs-source-dirs: test- main-is: Spec.hs- build-depends: base- , madlang- , hspec- , megaparsec- , text- , mtl- , hspec-megaparsec- ghc-options: -threaded -rtsopts -with-rtsopts=-N - default-language: Haskell2010+ type: exitcode-stdio-1.0+ main-is: Spec.hs+ build-depends:+ base >=4.9.1.0 && <4.10,+ madlang >=2.1.1.0 && <2.2,+ hspec >=2.4.3 && <2.5,+ megaparsec >=5.2.0 && <5.3,+ text >=1.2.2.1 && <1.3,+ mtl >=2.2.1 && <2.3,+ hspec-megaparsec >=0.3.1 && <0.4+ default-language: Haskell2010+ hs-source-dirs: test+ ghc-options: -threaded -rtsopts -with-rtsopts=-N -source-repository head- type: git- location: https://github.com/vmchale/madlang+benchmark tweeths-bench+ + if flag(llvm-fast)+ ghc-options: -threaded -rtsopts -with-rtsopts=-N -fllvm -optlo-O3 -O3+ else+ ghc-options: -threaded -rtsopts -with-rtsopts=-N -O3+ type: exitcode-stdio-1.0+ main-is: Bench.hs+ build-depends:+ base >=4.9.1.0 && <4.10,+ criterion >=1.1.4.0 && <1.2,+ madlang >=2.1.1.0 && <2.2,+ megaparsec >=5.2.0 && <5.3,+ text >=1.2.2.1 && <1.3+ default-language: Haskell2010+ hs-source-dirs: bench
src/Text/Madlibs/Ana/Parse.hs view
@@ -12,8 +12,10 @@ import qualified Text.Megaparsec.Lexer as L import Data.Monoid import Control.Monad+import qualified Data.Map as M import Control.Monad.State import Control.Exception hiding (try)+import Data.Char import Data.Composition -- | Parse a lexeme, aka deal with whitespace nicely. @@ -75,17 +77,32 @@ name :: Parser String name = lexeme (some (letterChar <|> oneOf ("-/" :: String))) <?> "template name" +-- | Parse a modifier+modifier :: Parser (T.Text -> T.Text)+modifier = do+ char '.'+ str <- try (string "to_upper") <|> (string "to_lower") + let modifiers = M.fromList [("to_upper", T.map toUpper), ("to_lower", T.map toLower)] in + pure $ maybe id id (M.lookup str modifiers)+ -- | Parse template into a `PreTok` of referents and strings preStr :: [T.Text] -> Parser PreTok-preStr ins = (fmap (Name . T.pack) name) <|>+preStr ins = do { + n <- name ;+ mod <- optional $ modifier ;+ spaceConsumer ;+ pure $ Name (T.pack n) (maybe id id mod)+ } <|> do { v <- var ;+ spaceConsumer ; pure . PreTok $ ins `access` (v-1) -- ins !! (v - 1) } <|> do { s <- quote (many $ noneOf ("\n\"" :: String)) ;+ mod <- optional $ modifier ; spaceConsumer ;- pure $ PreTok . T.pack $ s+ pure $ PreTok . (maybe id id mod) . T.pack $ s } <?> "string or function name" @@ -101,10 +118,9 @@ inclusions :: Parser [String] inclusions = many . try $ do include- str <- name+ str <- name -- make this broader string ".mad"- pure (str ++ ".mad")- -- still needs dependency resolution but eh+ pure (str ++ ".mad") -- TODO dependency resolution (?) -- | Parse a `define` block definition :: [T.Text] -> Parser (Key, [(Prob, [PreTok])])
src/Text/Madlibs/Ana/ParseUtils.hs view
@@ -24,7 +24,7 @@ concatTok param pretoks = do ctx <- get let unList (List a) = a- let toRand (Name str) = List . snd . (head' str param) . (filter ((== str) . fst)) . (map (second unList)) $ ctx-- TODO fix head' which can fail because of lack of scope too+ let toRand (Name str f) = (apply f) . List . snd . (head' str param) . (filter ((== str) . fst)) . (map (second unList)) $ ctx-- TODO fix head' which can fail because of lack of scope too toRand (PreTok txt) = Value txt fold . (map toRand) <$> pretoks @@ -33,7 +33,7 @@ buildTok param pretoks = do ctx <- get let unList (List a) = a- let toRand (Name str) = List . snd . (head' str param) . (filter ((==str) . fst)) . (map (second unList)) $ ctx+ let toRand (Name str f) = (apply f) . List . snd . (head' str param) . (filter ((==str) . fst)) . (map (second unList)) $ ctx -- TODO use modifier alongside name? toRand (PreTok txt) = Value txt List . zip ([1..]) . (map toRand) <$> pretoks
src/Text/Madlibs/Internal/Types.hs view
@@ -20,12 +20,16 @@ type Key = T.Text -- | Pretoken, aka token as first read in-data PreTok = Name T.Text | PreTok T.Text- deriving (Show, Eq)+data PreTok = Name T.Text (T.Text -> T.Text) | PreTok T.Text+ --deriving (Show, Eq) -- | datatype for a token returning a random string data RandTok = List [(Prob, RandTok)] | Value T.Text deriving (Show, Eq)++apply :: (T.Text -> T.Text) -> RandTok -> RandTok -- make a base functor so we can map f over stuff?+apply f (Value str) = Value (f str)+apply f (List l) = List $ map (over _2 (apply f)) l -- | Function to transform a `RandTok` into a `Tree String` so that it can be pretty-printed. --
src/Text/Madlibs/Internal/Utils.hs view
@@ -52,7 +52,7 @@ -- | Strip a pre-token's name unTok :: PreTok -> T.Text unTok (PreTok txt) = ""-unTok (Name txt) = txt+unTok (Name txt _) = txt -- | Read a file in as a `Text` readFile' :: FilePath -> IO T.Text
test/Spec.hs view
@@ -15,9 +15,9 @@ main :: IO () main = hspec $ do describe "parseTok" $ do- parallel $ it "parses a .mad string" $ do+ parallel $ it "parses a .mad string with modifiers" $ do file <- madFile- parseTok "" [] [] file `shouldParse` (List [(1.0,List [(0.5,Value "heads"),(0.5,Value "tails")])])+ parseTok "" [] [] file `shouldParse` (List [(1.0,List [(0.5,Value "HEADS"),(0.5,Value "tails")])]) parallel $ it "fails when quotes aren't closed" $ do file <- madFileFailure parseTok "" [] [] `shouldFailOn` file@@ -39,7 +39,9 @@ -- file <- madFileTibetan -- parseTok "" [] file `shouldParse` (List [(1.0,List [(0.5,Value "heads"),(0.5,Value "tails")])]) describe "runFile" $ do- parallel $ it "parses file with inclusions" $ \file -> do+ parallel $ it "parses nested modifiers correctly" $ \file -> do+ runFile [] "test/templates/modifiers.mad" >>= (`shouldSatisfy` (\a -> any (a==) ["AAAaaa","AAAaa","AAaaa","AAaa"]))+ parallel $ it "parses file with inclusions and modifiers on functions" $ \file -> do runFile [] "test/templates/include.mad" >>= (`shouldSatisfy` (\a -> any (a==) ["heads","tails","on its side"])) parallel $ it "runs on a file out of order" $ \file -> do runFile [] "test/templates/ordered.mad" >>= (`shouldSatisfy` (\a -> any (a==) ["heads","tails","one","two","three","third"]))
test/templates/gambling.mad view
@@ -1,5 +1,5 @@ :define something- 0.5 "heads"- 0.5 "tails"+ 0.5 "heads".to_upper+ 0.5 "tailS".to_lower :return 1.0 something
test/templates/include.mad view
@@ -1,6 +1,6 @@ :include gambling.mad :define realistic- 1.0 something+ 1.0 something.to_lower 0.03 "on its side" :return 1.0 realistic
+ test/templates/modifiers.mad view
@@ -0,0 +1,7 @@+:define scream+ 1.0 "aaa".to_upper+ 1.0 "aa".to_upper+:define sedated+ 1.0 scream.to_lower+:return+ 1.0 scream sedated