Spintax-0.3.7.0: src/Text/Spintax.hs
{-# LANGUAGE FlexibleContexts #-}
module Text.Spintax where
import Control.Applicative ((<|>))
import Control.Monad.Reader (ask, runReaderT)
import Data.Attoparsec.Text
import Data.List as L (intersperse)
import qualified Data.List.Extra as E
import qualified Data.Text as T
import System.Random.MWC
-- | Generate random texts based on a spinning syntax template, with nested alternatives and empty options.
--
-- >λ> spintax "{{Oh my God|Awesome|Can't believe that}, {a|the}|A|The} {quick {and dirty |||}||}{brown |pink |grey |black |yellow }{fox|flea|elephant|panther|bear} jumps over {the|a} {lazy |smelly |sleepy |grouchy }{dog|cat|whale}{|||, that's {really |||}amazing}{.|!|...}"
-- > Right "Awesome, the quick pink fox jumps over a sleepy whale."
--
spintax :: T.Text -> IO (Either String T.Text)
spintax template =
createSystemRandom >>= runReaderT (spin template)
where
spin t = go T.empty [] t (0::Int)
where
go o as i l
| l < 0 = parseFail
| l == 0 =
case parse spinSyntax i of
Done r m ->
case m of
"{" -> go o as r (l+1)
n | n == "}" || n == "|" -> parseFail
_ -> go (o <> m) as r l
Partial _ -> pure (Right $ o <> i)
Fail {} -> parseFail
| l == 1 =
case parse spinSyntax i of
Done r m ->
case m of
"{" -> go o (addAlter m) r (l+1)
"}" -> do
a <- spin =<< randAlter =<< ask
case a of
Right t' -> go (o <> t') [] r (l-1)
Left _ -> parseFail
"|" ->
if E.null as
then go o ["",""] r l
else go o (E.snoc as "") r l
_ -> go o (addAlter m) r l
Partial _ -> parseFail
Fail {} -> parseFail
| l > 1 =
case parse spinSyntax i of
Done r m ->
case m of
"{" -> go o (addAlter m) r (l+1)
"}" -> go o (addAlter m) r (l-1)
_ -> go o (addAlter m) r l
Partial _ -> parseFail
Fail {} -> parseFail
where
addAlter n =
case E.unsnoc as of
Just (xs,x) -> E.snoc xs (x <> n)
Nothing -> pure n
randAlter g =
(\r -> (!!) as (r-1)) <$> uniformR (1,E.length as) g
go _ _ _ _ = parseFail
parseFail = fail "Spintax template parsing failure"
spinSyntax =
openBrace <|> closeBrace <|> pipe <|> content
where
openBrace = string "{"
closeBrace = string "}"
pipe = string "|"
content =
takeWhile1 ctt
where
ctt '{' = False
ctt '}' = False
ctt '|' = False
ctt _ = True
-- * Utils
-- | Write a spintax alternative
--
-- >λ> writeSpintaxAlternative ["apple","apricot","banana","coconut"]
-- "{apple|apricot|banana|coconut}"
writeSpintaxAlternative :: [T.Text] -> T.Text
writeSpintaxAlternative = writeSpintaxExpression "|"
writeSpintaxExpression :: T.Text -> [T.Text] -> T.Text
writeSpintaxExpression s l = "{" <> T.concat (L.intersperse s l) <> "}"