packages feed

madlang 1.1.3.0 → 2.0.0.0

raw patch · 7 files changed

+161/−77 lines, 7 filesdep +containersdep +optparse-applicativedep −gitrevdep −optparse-genericdep ~madlang

Dependencies added: containers, optparse-applicative

Dependencies removed: gitrev, optparse-generic

Dependency ranges changed: madlang

Files

madlang.cabal view
@@ -1,5 +1,5 @@ name: madlang-version: 1.1.3.0+version: 2.0.0.0 cabal-version: >=1.10 build-type: Simple license: BSD3@@ -35,12 +35,12 @@         base >=4.7 && <5,         megaparsec >=5.2.0 && <5.3,         text >=1.2.2.1 && <1.3,-        optparse-generic >=1.1.4 && <1.2,+        optparse-applicative >=0.13.2.0 && <0.14,         mwc-random >=0.13.5.0 && <0.14,         lens >=4.15.1 && <4.16,         mtl >=2.2.1 && <2.3,         ansi-wl-pprint >=0.6.7.3 && <0.7,-        gitrev >=1.2.0 && <1.3+        containers >=0.5.7.1 && <0.6     default-language: Haskell2010     default-extensions: OverloadedStrings DeriveGeneric DeriveFunctor                         DeriveAnyClass@@ -63,7 +63,7 @@     main-is: Main.hs     build-depends:         base >=4.9.1.0 && <4.10,-        madlang >=1.1.3.0 && <1.2+        madlang >=2.0.0.0 && <2.1     default-language: Haskell2010     hs-source-dirs: app @@ -72,7 +72,7 @@     main-is: Spec.hs     build-depends:         base >=4.9.1.0 && <4.10,-        madlang >=1.1.3.0 && <1.2,+        madlang >=2.0.0.0 && <2.1,         hspec >=2.4.2 && <2.5,         megaparsec >=5.2.0 && <5.3,         text >=1.2.2.1 && <1.3,
src/Text/Madlibs/Ana/Parse.hs view
@@ -13,8 +13,6 @@ import Data.Monoid import Control.Monad import Control.Monad.State----import System.IO.Unsafe  -- | Parse a lexeme, aka deal with whitespace nicely.  lexeme :: Parser a -> Parser a@@ -32,13 +30,14 @@ float :: Parser Prob float = lexeme L.float +-- | Parse an integer integer :: Parser Integer integer = lexeme L.integer  -- | Make sure definition blocks start un-indented nonIndented = L.nonIndented spaceConsumer---nonIndented = L.indentGuard spaceConsumer EQ (unsafePos 1) +-- | Make contents of definition blocks are indented. indentGuard = L.indentGuard spaceConsumer GT (unsafePos 4)  -- | Parse between quotes@@ -74,13 +73,13 @@ preStr ins = (fmap (Name . T.pack) name) <|>     do {         v <- var ;-        pure . PreTok $ ins !! (v - 1)+        pure . PreTok $ ins `access` (v-1) -- ins !! (v - 1)     } <|>     do {         s <- quote (many $ noneOf ("\"" :: String)) ;         spaceConsumer ;         pure $ PreTok . T.pack $ s-    } --needs to stop on \n though+    }      <?> "string or function name"  -- | Parse a probability/corresponding template@@ -89,7 +88,7 @@     indentGuard     p <- float     str <- some $ (preStr ins)-    pure (p, str)+    pure (p, str) <?> "Probability/text pair"  -- | Parse a `define` block definition :: [T.Text] -> Parser (Key, [(Prob, [PreTok])])@@ -97,7 +96,7 @@     define     str <- name     val <- fmap normalize . some $ pair ins-    pure (T.pack str, val)+    pure (T.pack str, val) <?> "define block"  -- | Parse the `:return` block final :: [T.Text] -> Parser [(Prob, [PreTok])]@@ -112,6 +111,9 @@     p <- many (try (definition ins) <|> ((,) "Template" <$> final ins))     pure p +parseTreeM :: [T.Text] -> Parser (Context RandTok)+parseTreeM ins = buildTree <$> program ins+ -- | Parse text as a token + context (aka a reader monad with all the other functions) parseTokM :: [T.Text] -> Parser (Context RandTok) parseTokM ins = build <$> program ins@@ -120,5 +122,8 @@ -- -- > f <- readFile "template.mad" -- > parseTok f-parseTok :: [T.Text] -> T.Text -> Either (ParseError Char Dec) RandTok-parseTok ins f = snd . head . (filter (\(i,j) -> i == "Template")) . (flip execState []) <$> runParser (parseTokM ins) "" f+parseTok :: FilePath -> [T.Text] -> T.Text -> Either (ParseError Char Dec) RandTok+parseTok filename ins f = snd . head . (filter (\(i,j) -> i == "Template")) . (flip execState []) <$> runParser (parseTokM ins) filename f++parseTree :: FilePath -> [T.Text] -> T.Text -> Either (ParseError Char Dec) RandTok+parseTree filename ins f = snd . head . (filter (\(i,j) -> i == "Template")) . (flip execState []) <$> runParser (parseTreeM ins) filename f
src/Text/Madlibs/Ana/ParseUtils.hs view
@@ -18,25 +18,42 @@ 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+    let toRand (Name str) = List . snd . (head' str param) . (filter ((== str) . fst)) . (map (second unList)) $ ctx         toRand (PreTok txt) = Value txt     fold . (map toRand) <$> pretoks +buildTok :: T.Text -> Context [PreTok] -> Context RandTok+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+        toRand (PreTok txt) = Value txt+    List . zip ([1..]) . (map toRand) <$> pretoks++buildTree :: [(Key, [(Prob, [PreTok])])] -> Context RandTok+buildTree list@[(key,pairs)] = do+    toks <- mapM (\(i,j) -> buildTok key (pure j)) pairs+    let probs = map fst pairs+    let tok = List $ zip probs toks+    state (\s -> (tok,((key,tok):s)))+buildTree list@(x:xs) = do+    y <- buildTree [x]+    ys <- pure <$> buildTree xs+    pure . List . zip ([1..]) $ (y:ys)++-- basically just substitute? + -- | Given keys naming the tokens, and lists of `PreTok`, build our `RandTok` build :: [(Key, [(Prob, [PreTok])])] -> Context RandTok-build list-    | length list == 1 = do-        let [(key, pairs)] = list-        toks <- mapM (\(i,j) -> concatTok key (pure j)) pairs-        let probs = map fst pairs-        let tok = List $ zip probs toks-        state (\s -> (tok,((key, tok):s)))-        --should do: recurse or take "Template" key-    | otherwise = do-        let (x:xs) = list-        y <- (build [x])-        ys <- pure <$> build xs-        pure $ fold (y:ys)+build list@[(key,pairs)] = do+    toks <- mapM (\(i,j) -> concatTok key (pure j)) pairs+    let probs = map fst pairs+    let tok = List $ zip probs toks+    state (\s -> (tok,((key, tok):s)))+build list@(x:xs) = do+    y <- (build [x])+    ys <- pure <$> build xs+    pure $ fold (y:ys)  -- | Sort the keys that we have parsed so that dependencies are in the correct places sortKeys :: [(Key, [(Prob, [PreTok])])] -> [(Key, [(Prob, [PreTok])])]
src/Text/Madlibs/Cata/SemErr.hs view
@@ -8,7 +8,7 @@ import qualified Data.Text as T  -- | Datatype for a semantic error-data SemanticError = OverloadedReturns | CircularFunctionCalls T.Text T.Text | ProbSum T.Text+data SemanticError = OverloadedReturns | CircularFunctionCalls T.Text T.Text | ProbSum T.Text | InsufficientArgs Int Int     deriving (Typeable)  --also consider overloading parseError tbqh@@ -16,6 +16,7 @@ instance Show SemanticError where     show OverloadedReturns = show $ semErrStart <> text "File contains multiple declarations of :return"     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)     show (ProbSum f) = show $ semErrStart <> text "Function's options do not sum to 1:\n" <> indent 4 (yellow (text' f))     --we probably want to do our instance of `Show` for `ParseError` since that will let us color the position nicely @ least @@ -35,6 +36,9 @@ checkSemantics = foldr (.) id [ checkProb                               , checkReturn ] +-- checker to verify we have the right number of command-line args+-- checkArgs :: [(Key, [(Prob, [PreTok])])] -> [(Key, [(Prob, [PreTok])])]+ -- | checker to verify probabilities sum to 1 checkProb :: [(Key, [(Prob, [PreTok])])] -> [(Key, [(Prob, [PreTok])])] checkProb = map (\(i,j) -> if sumProb j then (i,j) else throw (ProbSum i))@@ -49,6 +53,9 @@ head' :: T.Text -> T.Text -> [a] -> a head' _ _ (x:xs) = x head' f1 f2 _ = throw (CircularFunctionCalls f1 f2)++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])])] -> [(Key, [(Prob, [PreTok])])]
src/Text/Madlibs/Exec/Main.hs view
@@ -1,7 +1,3 @@-{-# LANGUAGE TemplateHaskell #-}-{-# LANGUAGE DataKinds #-}-{-# LANGUAGE TypeOperators #-}- -- | Provides `madlang` runMadlangutable module Text.Madlibs.Exec.Main where @@ -13,67 +9,103 @@ import qualified Data.Text as T import qualified Data.Text.IO as TIO import Text.Megaparsec-import Options.Generic-import Development.GitRev+import Options.Applicative hiding (ParseError)+import Data.Monoid+--+import Data.Tree  -- | datatype for the program-data Program = Program { input :: FilePath <?> "filepath to template"-                       , debug :: Bool <?> "whether to display parsed RandTok" -                       , rep :: Maybe Int <?> "How many times to repeat"-                       , v :: [String] <?> "Extra input to the template" --fix soon?-                       , version :: Bool <?> "Display version information for debugging"-                       } deriving (Generic)+data Program = Program { sub :: Subcommand +                       , input :: FilePath +                       } -data Subcommand = Debug { file :: FilePath } -                | Run { file :: FilePath , rep' :: Maybe Int , input' :: [String] } +data Subcommand = Debug { version :: Bool }+                | Run { rep :: Maybe Int , clInputs :: [String] }+                | Lint { clInputs :: [String] }+                -- Repeat { rep :: Maybe Int } --- | Generated automatically by optparse-generic.-instance ParseRecord Program where+orders :: Parser Program+orders = Program+    <$> (hsubparser+        (command "run" (info temp (progDesc "Generate text from a .mad file"))+        <> command "debug" (info debug (progDesc "Debug a template"))+        <> command "lint" (info lint (progDesc "Lint a file"))))+    <*> (argument str+        (metavar "FILEPATH"+        <> help "File path to madlang template")) +debug :: Parser Subcommand+debug = Debug+    <$> switch+        (long "version"+        <> short 'v'+        <> help "Show version information")++temp :: Parser Subcommand+temp = Run+    <$> (optional $ read <$> strOption+        (long "rep"+        <> short 'r'+        <> metavar "REPETITIONS"+        <> help "Number of times to repeat"))+    <*> (many $ strOption+        (short 'i'+        <> metavar "VAR"+        <> help "command-line inputs to the template."))++lint :: Parser Subcommand+lint = Lint+    <$> (many $ strOption+        (short 'i'+        <> metavar "VAR"+        <> help "command-line inputs to the template."))+ -- | Main program action runMadlang :: IO ()-runMadlang = do-    x <- getRecord "Text.Madlibs templating DSL"-    if unHelpful . version $ x then putStrLn build else pure ()-    case unHelpful . rep $ x of-        (Just n) -> replicateM_ n $ template x-        Nothing -> template x+runMadlang = execParser wrapper >>= template +    --case rep . sub $ x of+    --    (Just n) -> replicateM_ n $ template x+    --    Nothing -> template x +wrapper = info (helper <*> orders)+    (fullDesc+    <> progDesc "Madlang templating language"+    <> header "Madlang - markov chains made easy")+ -- | given a parsed record perform the appropriate IO action template :: Program -> IO () template rec = do-    let filepath = unHelpful . input $ rec-    let ins = map T.pack $ (unHelpful . v $ rec)+    let filepath = input $ rec+    let ins = map T.pack $ (clInputs . sub $ rec)     parsed <- parseFile ins filepath-    runFile ins filepath >>= TIO.putStrLn-    if unHelpful . debug $ rec then-        print parsed-    else-        pure ()+    case sub rec of+        (Run reps _) -> do+            replicateM_ (maybe 1 id reps) $ runFile ins filepath >>= TIO.putStrLn -- fix so it parses once!! either show run $ parsed +        (Debug _) -> do+            putStrLn . (either show (drawTree . tokToTree)) =<< makeTree ins filepath -- parsed+            --print parsed+        (Lint _) -> do+            putStrLn $ either show (const "No errors found.") parsed  -- | Generate randomized text from a template-templateGen :: [T.Text] -> T.Text -> Either (ParseError Char Dec) (IO T.Text)-templateGen ins txt = run <$> parseTok ins txt+templateGen :: FilePath -> [T.Text] -> T.Text -> Either (ParseError Char Dec) (IO T.Text)+templateGen filename ins txt = run <$> parseTok filename ins txt  -- | Generate randomized text from a file conatining a template runFile :: [T.Text] -> FilePath -> IO T.Text runFile ins filepath = do     txt <- readFile' filepath-    either (pure . parseErrorPretty') (>>= (pure . show')) (templateGen ins txt)+    either (pure . parseErrorPretty') (>>= (pure . show')) (templateGen filepath ins txt)  -- | Parse a template file into the `RandTok` data type parseFile :: [T.Text] -> FilePath -> IO (Either (ParseError Char Dec) RandTok) parseFile ins filepath = do     txt <- readFile' filepath-    let val = parseTok ins txt+    let val = parseTok filepath ins txt     pure val --- | String with git commit string-build :: String-build = concat [ "[version: ", $(gitBranch), "@", $(gitHash)-               , " (", $(gitCommitDate), ")"-               , " (", $(gitCommitCount), " commits in HEAD)"-               , dirty, "] "] -    where-        dirty | $(gitDirty) = " (uncommitted files present)" -              | otherwise   = ""+makeTree :: [T.Text] -> FilePath -> IO (Either (ParseError Char Dec) RandTok)+makeTree ins filepath = do+    txt <- readFile' filepath+    let val = parseTree filepath ins txt+    pure val
src/Text/Madlibs/Internal/Types.hs view
@@ -10,6 +10,8 @@ import Control.Lens hiding (List, Context) import Data.Function import Data.Monoid+--+import Data.Tree  -- | datatype for a double representing a probability type Prob = Double@@ -25,6 +27,27 @@ data RandTok = List [(Prob, RandTok)] | Value T.Text     deriving (Show, Eq) +tokToTree :: RandTok -> Tree String+tokToTree (Value a) = Node (show a) []+tokToTree (List xs) = Node "++" (map (tokToTree . snd) xs)++-- | Neat 2-dimensional drawing of a parsed tree.+{--+drawTree :: Tree String -> String+drawTree  = unlines . draw+++draw :: Tree String -> [String]+draw (Node x ts0) = lines x ++ drawSubTrees ts0+  where+    drawSubTrees [] = []+    drawSubTrees [t] =+        "|" : shift "`- " "   " (draw t)+    drawSubTrees (t:ts) =+        "|" : shift "+- " "|  " (draw t) ++ drawSubTrees ts++    shift first other = zipWith (++) (first : repeat other)+--} -- | Make `RandTok` a monoid so we can append them together nicely (since they do generate text).  -- -- > (Value "Hello") <> (List [(0.5," you"), (0.5, " me")])
test/Spec.hs view
@@ -15,19 +15,19 @@ main = hspec $ do     describe "parseTok" $ do         parallel $ it "parses a .mad string" $ do-            parseTok [] madFile `shouldParse` (List [(1.0,List [(0.5,Value "heads"),(0.5,Value "tails")])])+            parseTok "" [] madFile `shouldParse` (List [(1.0,List [(0.5,Value "heads"),(0.5,Value "tails")])])         parallel $ it "fails when quotes aren't closed" $ do-            parseTok [] `shouldFailOn` madFileFailure+            parseTok "" [] `shouldFailOn` madFileFailure         parallel $ it "parses when functions are out of order" $ do-            parseTok [] `shouldSucceedOn` madComplexFile+            parseTok "" [] `shouldSucceedOn` madComplexFile         parallel $ it "returns a correct string from the template when evaluating a token" $ do             (testIO . run) exampleTok `shouldSatisfy` (\a -> on (||) (a ==) "heads" "tails")         parallel $ it "throws exception when two `:return`s are declared" $ do-            (parseTok [] `shouldFailOn` semErrFile) `shouldThrow` semErr+            (parseTok "" [] `shouldFailOn` semErrFile) `shouldThrow` semErr         parallel $ it "substitutes a variable correctly" $ do-            parseTok ["maxine"] `shouldSucceedOn` madVar+            parseTok "" ["maxine"] `shouldSucceedOn` madVar         parallel $ it "fails when variables are not passed in" $ do-            (parseTok [] `shouldFailOn` madVar) `shouldThrow` anyException+            (parseTok "" [] `shouldFailOn` madVar) `shouldThrow` anyException  semErr :: Selector SemanticError semErr = const True