packages feed

madlang 2.1.1.3 → 2.1.2.0

raw patch · 15 files changed

+138/−70 lines, 15 filesdep ~basePVP: major bump suggested

API removals or changes: PVP suggests a major version bump

Dependency ranges changed: base

API changes (from Hackage documentation)

- Text.Madlibs: OverloadedReturns :: SemanticError
+ Text.Madlibs: DoubleDefinition :: Text -> SemanticError
+ Text.Madlibs: NoContext :: Text -> SemanticError
+ Text.Madlibs: NoReturn :: SemanticError

Files

madlang.cabal view
@@ -1,5 +1,5 @@ name:                madlang-version:             2.1.1.3+version:             2.1.2.0 synopsis:            Randomized templating language DSL description:         Please see README.md homepage:            https://github.com/vmchale/madlang#readme@@ -12,14 +12,9 @@ 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/modifiers.mad+                   , test/templates/*.mad                    , test/templates/err/*.mad-                   , demo/shakespeare.mad+                   , demo/*.mad                    , default.nix                    , release.nix                    , stack.yaml@@ -41,6 +36,7 @@                      , Text.Madlibs.Internal.Types                      , Text.Madlibs.Internal.Utils                      , Text.Madlibs.Cata.SemErr+                     , Text.Madlibs.Cata.Display                      , Text.Madlibs.Exec.Main   build-depends:       base >= 4.7 && < 5                      , megaparsec@@ -58,6 +54,7 @@   default-extensions:  OverloadedStrings                      , DeriveGeneric                      , DeriveFunctor+                     , DeriveAnyClass  executable madlang   hs-source-dirs:      app
src/Text/Madlibs.hs view
@@ -1,6 +1,6 @@ -- | Main module exporting the relevant functions module Text.Madlibs (-                    -- * Parsers for `.mad` files+                    -- * Parsers for @.mad@ files                       parseTok                     , parseTokM                     , runFile
src/Text/Madlibs/Ana/Parse.hs view
@@ -11,6 +11,7 @@ import Text.Megaparsec.Char import qualified Text.Megaparsec.Lexer as L import Data.Monoid+import Data.Maybe import Control.Monad import qualified Data.Map as M import Control.Monad.State@@ -31,7 +32,7 @@  -- | Parse a number/probability float :: Parser Prob-float = lexeme L.float <|> (fromIntegral <$> integer) <?> "Float"+float = lexeme L.float <|> (fromIntegral <$> integer) <?> "Number"  -- | Parse an integer integer :: Parser Integer@@ -45,7 +46,7 @@  -- | Parse between quotes quote :: Parser a -> Parser a-quote = between (char '"') (char '"') +quote = between .$ (char '"')  -- | Parse a keyword keyword :: String -> Parser String@@ -74,14 +75,14 @@  -- | Parse a template name (what follows a `:define` or `return` block) name :: Parser String-name = lexeme (some (letterChar <|> oneOf ("-/" :: String))) <?> "template name"+name = lexeme (some (letterChar <|> oneOf ("-/" :: String))) <?> "template name" -- TODO make this broader in terms of what it includes  -- | Parse a modifier modifier :: Parser (T.Text -> T.Text) modifier = do     char '.'-    str <- try (string "to_upper") <|> (string "to_lower")-    pure $ maybe id id (M.lookup str modifierList)+    str <- (try $ string "to_upper") <|> (string "to_lower")+    pure (maybe id id (M.lookup str modifierList)) <?> "modifier"  -- | Parse template into a `PreTok` of referents and strings preStr :: [T.Text] -> Parser PreTok@@ -99,7 +100,7 @@     } <|>     do {         s <- quote (many $ noneOf ("\n\"" :: String)) ;-        mod <- many $ modifier ; -- TODO: parse many via a fold?+        mod <- many $ modifier ;         spaceConsumer ;         pure . PreTok . (foldr (.) id mod) . T.pack $ s     } @@ -111,15 +112,15 @@     indentGuard     p <- float     str <- some (preStr ins)-    pure (p, str) <?> "Probability/text pair"+    pure (p, str) <?> "Probability-text pair"  -- | Parse an `include` inclusions :: Parser [String] inclusions = many . try $ do     include-    str <- name -- make this broader+    str <- name     string ".mad"-    pure (str ++ ".mad") -- TODO dependency resolution (?)+    pure (str ++ ".mad")  -- | Parse a `define` block definition :: [T.Text] -> Parser (Key, [(Prob, [PreTok])])@@ -140,7 +141,7 @@ program :: [T.Text] -> Parser [(Key, [(Prob, [PreTok])])] program ins = sortKeys <$> (checkSemantics =<< do     inclusions-    p <- many (try (definition ins) <|> ((,) "Template" <$> final ins))+    p <- many (try (definition ins) <|> ((,) "Return" <$> final ins))     lexeme eof     pure p) @@ -155,18 +156,19 @@ -- | Parse text as a list of functions parseTokF :: FilePath -> [(Key, RandTok)] -> [T.Text] -> T.Text -> Either (ParseError Char Dec) [(Key, RandTok)] parseTokF filename state ins f = (flip execState (filterTemplate state)) <$> runParser (parseTokM ins) filename f -    where filterTemplate = map (\(i,j) -> if i == "Template" then (strip filename, j) else (i,j)) -- problem: doesn't tell what file we're reading FROM+    where filterTemplate = map (\(i,j) -> if i == "Return" then (strip filename, j) else (i,j)) -- TODO fix the extras  -- | Parse text as a list of tokens, suitable for printing as a tree. parseTreeF :: FilePath -> [(Key, RandTok)] -> [T.Text] -> T.Text -> Either (ParseError Char Dec) [(Key, RandTok)] parseTreeF filename state ins f = (flip execState (filterTemplate state)) <$> runParser (parseTreeM ins) filename f -    where filterTemplate = map (\(i,j) -> if i == "Template" then (strip filename, j) else (i,j))+    where filterTemplate = map (\(i,j) -> if i == "Return" then (strip filename, j) else (i,j)) --- | Parse text as a token------ > f <- readFile "template.mad"--- > parseTok f -- | Parse text given a context+--+-- > import qualified Data.Text.IO as TIO+-- >+-- > f <- TIO.readFile "template.mad"+-- > parseTok "filename.mad" [] [] f parseTok :: FilePath -> [(Key, RandTok)] -> [T.Text] -> T.Text -> Either (ParseError Char Dec) RandTok parseTok = (fmap takeTemplate) .*** parseTokF 
src/Text/Madlibs/Ana/ParseUtils.hs view
@@ -26,7 +26,7 @@  -- | Get the :return value takeTemplate :: [(Key, RandTok)] -> RandTok-takeTemplate = snd . head . filter (\(i,j) -> i == "Template")+takeTemplate = snd . head . filter (\(i,j) -> i == "Return")  -- | Convert the stuff after the number to a `RandTok` concatTok :: T.Text -> Context [PreTok] -> Context RandTok@@ -78,8 +78,8 @@ -- | Ordering on the keys to account for dependency orderKeys :: (Key, [(Prob, [PreTok])]) -> (Key, [(Prob, [PreTok])]) -> Ordering orderKeys (key1, l1) (key2, l2)-    | key1 == "Template" = GT-    | key2 == "Template" = LT+    | key1 == "Return" = GT+    | key2 == "Return" = LT     | any (\pair -> any (T.isInfixOf key1) (map unTok . snd $ pair)) l1 = GT     | any (\pair -> any (T.isInfixOf key2) (map unTok . snd $ pair)) l1 = LT     | otherwise = EQ
src/Text/Madlibs/Ana/Resolve.hs view
@@ -1,3 +1,6 @@+{-# LANGUAGE FlexibleContexts #-}++ -- | Module containing IO stuff to get/parse files with external dependencies module Text.Madlibs.Ana.Resolve where @@ -10,18 +13,27 @@ import Text.Madlibs.Cata.Run import Data.Composition import System.Directory+import Lens.Micro+import Data.Monoid+import Control.Applicative+import Control.Monad  -- | Parse a template file into the `RandTok` data type-parseFile :: [T.Text] -> FilePath -> FilePath -> IO (Either (ParseError Char Dec) RandTok)+parseFile :: [T.Text] -- ^ variables to substitute into the template+    -> FilePath -- ^ folder+    -> FilePath -- ^ filepath within folder+    -> IO (Either (ParseError Char Dec) RandTok) -- ^ parsed `RandTok` parseFile = fmap (fmap takeTemplate) .** (getInclusionCtx False)  -- | Generate text from file with inclusions getInclusionCtx :: Bool -> [T.Text] -> FilePath -> FilePath -> IO (Either (ParseError Char Dec) [(Key, RandTok)]) getInclusionCtx isTree ins folder filepath = do     file <- readFile' (folder ++ filepath)-    let filenames = either (const []) id $ parseInclusions filepath file -- FIXME pass up errors correctly-    ctx <- mapM (getInclusionCtx isTree ins folder) filenames -    parseCtx isTree ins (concatMap (either (const []) id) ctx) (folder ++ filepath)+    let filenames = either (error . show) id $ parseInclusions filepath file -- TODO pass up errors correctly+    let resolveKeys file = map (over _1 ((((T.pack . (<> "-")) . dropExtension) $ file) <>))+    ctxPure <- mapM (getInclusionCtx isTree ins folder) filenames+    let ctx = (zipWith resolveKeys filenames) <$> sequence ctxPure+    parseCtx isTree ins (concat . (either (const []) id) $ ctx) (folder ++ filepath)  -- | Generate randomized text from a file conatining a template runFile :: [T.Text] -> FilePath -> IO T.Text@@ -36,10 +48,10 @@  -- | Run based on text input, with nothing linked. runText :: [T.Text] -> String -> T.Text -> IO T.Text-runText vars name txt = (either (pure . parseErrorPretty') id) . (fmap run) $ (parseTok name [] vars txt)+runText vars name = (either (pure . parseErrorPretty') id) . (fmap run) . (parseTok name [] vars)  -- | Get file as context-parseCtx :: Bool -> [T.Text] -> [(Key, RandTok)] -> FilePath -> IO (Either (ParseError Char Dec) [(Key, RandTok)]) +parseCtx :: Bool -> [T.Text] -> [(Key, RandTok)] -> FilePath -> IO (Either (ParseError Char Dec) [(Key, RandTok)]) parseCtx isTree ins state filepath = do     txt <- readFile' filepath     let keys = (if isTree then parseTreeF else parseTokF) filepath state ins txt
+ src/Text/Madlibs/Cata/Display.hs view
@@ -0,0 +1,16 @@+module Text.Madlibs.Cata.Display where++import Data.Tree+import Text.Madlibs.Internal.Types++-- | Draw as a syntax Tree+displayTree :: RandTok -> String+displayTree = drawTree . tokToTree 1.0++-- | Function to transform a `RandTok` into a `Tree String` so that it can be pretty-printed. +--+-- > tokToTree 1.0 tok+tokToTree :: Prob -> RandTok -> Tree String+tokToTree p (Value a) = Node ((take 4 . show . min 1.0) p ++ " " ++ show a) []+tokToTree p (List [(_,Value a)]) = Node ((take 4 . show . min 1.0) p ++ " " ++ show a) []+tokToTree p (List xs) = Node (take 4 . show . min 1.0 $ p) (map (uncurry tokToTree) xs)
src/Text/Madlibs/Cata/SemErr.hs view
@@ -1,3 +1,5 @@+{-# LANGUAGE FlexibleContexts #-}+ -- | Module defining the SemErr data type module Text.Madlibs.Cata.SemErr where @@ -7,18 +9,24 @@ import Control.Exception import qualified Data.Text as T import Control.Monad+import Lens.Micro+import Lens.Micro.Extras import qualified Data.Set as S import Text.Megaparsec.Text import Text.Megaparsec.Prim+import Data.Composition import Text.Megaparsec.Error+import Text.Madlibs.Internal.Utils  -- | Datatype for a semantic error-data SemanticError = OverloadedReturns | CircularFunctionCalls T.Text T.Text | InsufficientArgs Int Int+data SemanticError = NoReturn | CircularFunctionCalls T.Text T.Text | InsufficientArgs Int Int | DoubleDefinition T.Text | NoContext T.Text     deriving (Typeable)  -- | display a `SemanticError` nicely with coloration & whatnot instance Show SemanticError where-    show OverloadedReturns = show $ semErrStart <> text "File must contain exactly one declaration of :return"+    show (DoubleDefinition f) = show $ semErrStart <> text "File contains two declarations of:" <> indent 4 (yellow $ (text' f))+    show NoReturn = show $ semErrStart <> text "File must contain exactly one declaration of :return"+    show (NoContext f1) = show $ semErrStart <> text "Call in function: " <> indent 4 (yellow $ (text' f1)) <> "which is not in scope"     show (CircularFunctionCalls f1 f2) = show $ semErrStart <> text "Circular function declaration between:" <> indent 4 (yellow $ (text' f1) <> (text ", ") <> (text' f2))     show (InsufficientArgs i j) = show $ semErrStart <> text "Insufficent arguments from the command line, given " <> (text . show $ i) <> ", expected at least " <> (text . show $ j) @@ -27,19 +35,29 @@  -- | Throw custom error given by string, within the parser customError :: String -> Parser a-customError = failure S.empty S.empty . S.singleton . representFail+customError = (failure .$ S.empty) . S.singleton . representFail --- | Throw `OverloadedReturns` error within parser-overloadedReturns :: Parser a-overloadedReturns = customError . show $ OverloadedReturns+showCustomError :: (Show a) => a -> Parser b+showCustomError = customError . show +-- | Throw `NoReturn` error within parser+noReturn :: Parser a+noReturn = showCustomError $ NoReturn++noContext :: T.Text -> Parser a+noContext f1 = showCustomError $ NoContext f1+ -- | Throws argument for circular function calls circularFunctionCalls :: T.Text -> T.Text -> Parser a-circularFunctionCalls f1 f2 = customError . show $ CircularFunctionCalls f1 f2+circularFunctionCalls f1 f2 = showCustomError $ CircularFunctionCalls f1 f2 +-- | Throws error when a function is defined twice+doubleDefinition :: T.Text -> Parser a+doubleDefinition f = showCustomError $ DoubleDefinition f+ -- | Throws error for insufficient arguments insufficientArgs :: Int -> Int -> Parser a-insufficientArgs i j = customError . show $ InsufficientArgs i j+insufficientArgs i j = showCustomError $ InsufficientArgs i j  -- | Constant to start `SemanticError`s semErrStart :: Doc@@ -49,10 +67,14 @@ text' :: T.Text -> Doc text' = text . T.unpack +--do we need this all in a monad?? -- | big semantics checker that sequences stuff checkSemantics :: [(Key, [(Prob, [PreTok])])] -> Parser [(Key, [(Prob, [PreTok])])]-checkSemantics = foldr (<=<) pure [ checkReturn-                                  ] -- do we need this all to be in a monad? +checkSemantics keys = foldr (<=<) pure ((checkKey "Return"):[checkKey key | key <- allKeys keys ])+                                       $ keys+    where allKeys = fmap name . (concatMap snd) . (concatMap snd) -- TODO use a traversal here!!+          name (Name str _) = str+          name (PreTok _) = "Return"  -- | helper to filter out stuff that doesn't sumProb :: [(Prob, [PreTok])] -> Bool@@ -68,14 +90,20 @@ access :: [a] -> Int -> a access xs i = if (i >= length xs) then throw (InsufficientArgs (length xs) (i+1)) else xs !! i --- | checker to verify there is at most one `:return` statement-checkReturn :: [(Key, [(Prob, [PreTok])])] -> Parser [(Key, [(Prob, [PreTok])])]-checkReturn keys-    | singleReturn keys = pure keys-    | otherwise = overloadedReturns+-- | checker to verify there is at most one @:return@ or @:define key@ statement+checkKey :: Key -> [(Key, [(Prob, [PreTok])])] -> Parser [(Key, [(Prob, [PreTok])])]+checkKey key keys+    | singleInstance key keys = pure keys+    | noInstance key keys = pure keys -- noContext key -- FIXME only if it recurses properly!+    | key == "Return" && noInstance key keys = noReturn+    | otherwise = doubleDefinition key  -- | Checks that we have at most one `:return` template in the file-singleReturn :: [(Key, [(Prob, [PreTok])])] -> Bool-singleReturn = singleton . (filter ((=="Template") . fst))+singleInstance :: Key -> [(Key, [(Prob, [PreTok])])] -> Bool+singleInstance key = singleton . (filter ((==key) . fst))     where singleton [a] = True           singleton _   = False++-- | Checks that there are no instances of a key+noInstance :: Key -> [(Key, [(Prob, [PreTok])])] -> Bool+noInstance key = (== []) . (filter ((==key) . fst))
src/Text/Madlibs/Exec/Main.hs view
@@ -2,8 +2,8 @@ module Text.Madlibs.Exec.Main where  import Control.Monad-import Text.Madlibs.Cata.Run import Text.Madlibs.Ana.Resolve+import Text.Madlibs.Cata.Display import Text.Madlibs.Internal.Types import Text.Madlibs.Internal.Utils import qualified Data.Text as T@@ -13,8 +13,6 @@ import Data.Monoid import Data.Composition import System.Directory----import Text.Madlibs.Ana.Parse  -- | datatype for the program data Program = Program { sub :: Subcommand @@ -57,7 +55,6 @@         (short 'i'         <>  metavar "VAR"         <> help "command-line inputs to the template."))-        -- TODO consider making arguments nicer?  -- | Parser for the lint subcommand lint :: Parser Subcommand
src/Text/Madlibs/Internal/Types.hs view
@@ -21,8 +21,12 @@  -- | Pretoken, aka token as first read in data PreTok = Name T.Text (T.Text -> T.Text) | PreTok T.Text-    --deriving (Show, Eq) +instance Eq PreTok where+    (==) (Name a _) (PreTok b) = False+    (==) (Name a1 f1) (Name a2 f2) = a1 == a2 && (f1 . T.pack $ ['a'..'z']) == (f2 . T.pack $ ['a'..'z'])+    (==) (PreTok a) (PreTok b) = a == b+ -- | datatype for a token returning a random string data RandTok = List [(Prob, RandTok)] | Value T.Text     deriving (Show, Eq)@@ -30,14 +34,6 @@ 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. ------ > tokToTree 1.0 tok-tokToTree :: Prob -> RandTok -> Tree String-tokToTree p (Value a) = Node ((take 4 . show . min 1.0) p ++ " " ++ show a) []-tokToTree p (List [(_,Value a)]) = Node ((take 4 . show . min 1.0) p ++ " " ++ show a) []-tokToTree p (List xs) = Node (take 4 . show . min 1.0 $ p) (map (uncurry tokToTree) xs)  -- | Make `RandTok` a monoid so we can append them together nicely (since they do generate text).  --
src/Text/Madlibs/Internal/Utils.hs view
@@ -10,11 +10,10 @@ import qualified Data.Text as T import System.IO.Unsafe import Lens.Micro-import Data.Tree --- | Draw as a syntax Tree-displayTree :: RandTok -> String-displayTree = drawTree . tokToTree 1.0+-- | Drop file Extension+dropExtension :: FilePath -> FilePath+dropExtension = reverse . drop 1 . (dropWhile (/='.')) . reverse  -- | Get directory associated to a file getDir :: FilePath -> FilePath@@ -22,7 +21,7 @@  -- | Function to apply a value on both arguments, e.g. ----- > between .$ (symbol "\'")+-- > between .$ (char '"') (.$) :: (a -> a -> b) -> a -> b (.$) f x = f x x 
test/Spec.hs view
@@ -21,6 +21,9 @@         parallel $ it "fails when quotes aren't closed" $ do             file <- madFileFailure             parseTok "" [] [] `shouldFailOn` file+        parallel $ it "fails on nonsensical modifiers" $ do+            file <- readFile' "test/templates/err/bad-modifier.mad"+            parseTok "" [] [] `shouldFailOn` file         parallel $ it "parses when functions are out of order" $ do             file <- madComplexFile             parseTok "" [] [] `shouldSucceedOn` file@@ -43,6 +46,8 @@             runFile ["aa"] "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 "parses file with recursive inclusions" $ \file -> do+            runFile [] "test/templates/include-recursive.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/err/bad-modifier.mad view
@@ -0,0 +1,5 @@+:define main+    1.0 "loud string".to_opper+    1.0 "quiet string"+:return+    1.0 main
+ test/templates/err/double-declarations.mad view
@@ -0,0 +1,6 @@+:define something+    1.0 "something"+:define something+    1.0 "something else"+:return+    1.0 something
+ test/templates/include-recursive.mad view
@@ -0,0 +1,5 @@+:include include.mad+:define another+    1.0 include-gambling-something+:return+    1.0 another
test/templates/include.mad view
@@ -1,6 +1,6 @@ :include gambling.mad :define realistic-    1.0 something.to_lower+    1.0 gambling-something.to_lower     0.03 "on its side" :return     1.0 realistic