packages feed

madlang (empty) → 0.1.0.0

raw patch · 14 files changed

+650/−0 lines, 14 filesdep +ansi-wl-pprintdep +basedep +hspecsetup-changed

Dependencies added: ansi-wl-pprint, base, hspec, hspec-megaparsec, lens, madlang, megaparsec, mtl, mwc-random, optparse-generic, text

Files

+ LICENSE view
@@ -0,0 +1,30 @@+Copyright Vanessa McHale (c) 2017++All rights reserved.++Redistribution and use in source and binary forms, with or without+modification, are permitted provided that the following conditions are met:++    * Redistributions of source code must retain the above copyright+      notice, this list of conditions and the following disclaimer.++    * Redistributions in binary form must reproduce the above+      copyright notice, this list of conditions and the following+      disclaimer in the documentation and/or other materials provided+      with the distribution.++    * Neither the name of Vanessa McHale nor the names of other+      contributors may be used to endorse or promote products derived+      from this software without specific prior written permission.++THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ README.md view
@@ -0,0 +1,57 @@+## Madlibs DSL for generating random text++This is the Madlibs DSL for generating random text. There is also a vim plugin for highlighting `.mad` files. ++It enables you to generate random, templated text with very little effort or expertise. ++It can be used for twitter bots and more productive things.++### Exmaples++An exmaple is worth a thousand words (?), so suppose you wanted to generate a mediocre fortune telling bot. You could write the following code:++```++:define person+    0.7 "A close friend will "+    0.3 "You will "+:define goodfortune+    0.2 person "make rain on the planet Mars"+    0.8 "nice things will happen today :)"+:define fortune+    0.5 "drink a boatload of milk"+    0.5 "get angry for no reason"+:return+    0.8 person fortune+    0.2 goodfortune+```++There are two "statements" in madlang, `:define` and `:return`. `:return` is the main string we'll be spitting back, so you're only allowed one of them per file. `:define` on the other hand can be used to make as many templates as you want. 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`, you'll end up with either no fortune or an infinite fortune. So instead we just throw an error. ++## Installation++### Stack++Download `stack` with++```+curl -sSl http://haskellstack.org | sh+```++Then run `stack install` and you'll get the `madlang` executable installed on your path. You can even do `stack install madlang` if you'd like. ++### Use++To use it, just try++```+ $ madlang --input fortune-teller.mad+```++You can do `madlang --help` if you want a couple other options for debugging.++### Syntax Highlighting++Syntax highlighting for the DSL is provided in the vim plugin [here](http://github.com/vmchale/madlang-vim). You'll have to do `:set syntax=madlang` the first time you run it but everything else should work out of the box.:
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ app/Main.hs view
@@ -0,0 +1,6 @@+module Main where++import Text.Madlibs (exec)++main :: IO ()+main = exec
+ madlang.cabal view
@@ -0,0 +1,69 @@+name: madlang+version: 0.1.0.0+cabal-version: >=1.10+build-type: Simple+license: BSD3+license-file: LICENSE+copyright: Copyright: (c) 2016 Vanessa McHale+maintainer: tmchale@wisc.edu+homepage: https://github.com/vmchale/madlang#readme+synopsis: Initial project template from stack+description:+    Please see README.md+category: Web+author: Vanessa McHale+extra-source-files:+    README.md++source-repository head+    type: git+    location: https://github.com/vmchale/madlang++library+    exposed-modules:+        Text.Madlibs+    build-depends:+        base >=4.7 && <5,+        megaparsec >=5.0.1 && <5.1,+        text >=1.2.2.1 && <1.3,+        optparse-generic >=1.1.1 && <1.2,+        mwc-random >=0.13.5.0 && <0.14,+        lens ==4.14.*,+        mtl >=2.2.1 && <2.3,+        ansi-wl-pprint >=0.6.7.3 && <0.7+    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.Internal.Types+        Text.Madlibs.Internal.Utils+        Text.Madlibs.Cata.SemErr+        Text.Madlibs.Exec.Main++executable madlang+    main-is: Main.hs+    build-depends:+        base >=4.9.0.0 && <4.10,+        madlang >=0.1.0.0 && <0.2+    default-language: Haskell2010+    hs-source-dirs: app+    ghc-options: -threaded -rtsopts -with-rtsopts=-N -O2++test-suite madlang-test+    type: exitcode-stdio-1.0+    main-is: Spec.hs+    build-depends:+        base >=4.9.0.0 && <4.10,+        madlang >=0.1.0.0 && <0.2,+        hspec >=2.2.4 && <2.3,+        megaparsec >=5.0.1 && <5.1,+        text >=1.2.2.1 && <1.3,+        mtl >=2.2.1 && <2.3,+        hspec-megaparsec >=0.2.1 && <0.3+    default-language: Haskell2010+    hs-source-dirs: test+    ghc-options: -threaded -rtsopts -with-rtsopts=-N -O2
+ src/Text/Madlibs.hs view
@@ -0,0 +1,24 @@+-- | Main module with the import functions+module Text.Madlibs (+                    -- * Parsers for `.mad` files+                    parseTok+                    , runFile+                    , parseFile+                    , templateGen+                    -- * Functions and constructs for the `RandTok` data type+                    , run+                    , RandTok (..)+                    -- * Types associated with the parser+                    , Context+                    , SemanticError (..)+                    -- * Command-line executable+                    , exec+                    ) where++import Text.Madlibs.Ana.Parse+import Text.Madlibs.Ana.ParseUtils+import Text.Madlibs.Cata.Run+import Text.Madlibs.Cata.SemErr+import Text.Madlibs.Exec.Main+import Text.Madlibs.Internal.Types+import Text.Madlibs.Internal.Utils
+ src/Text/Madlibs/Ana/Parse.hs view
@@ -0,0 +1,109 @@+-- | Parse our DSL+module Text.Madlibs.Ana.Parse where++import Text.Madlibs.Internal.Types+import Text.Madlibs.Internal.Utils+import Text.Madlibs.Ana.ParseUtils+import Text.Madlibs.Cata.SemErr+import qualified Data.Text as T+import Text.Megaparsec+import Text.Megaparsec.Text+import Text.Megaparsec.Char+import qualified Text.Megaparsec.Lexer as L+import Data.Monoid+import Control.Monad+import Control.Monad.State++-- | Parse a lexeme, aka deal with whitespace nicely. +lexeme :: Parser a -> Parser a+lexeme = L.lexeme spaceConsumer++-- | space consumer with awareness for comments+spaceConsumer :: Parser ()+spaceConsumer = L.space (void . some $ spaceChar) (L.skipLineComment "#") (L.skipBlockComment "{#" "#}")++-- | parse a symbol, i.e. string plus surrouding whitespace+symbol :: String -> Parser String+symbol = L.symbol spaceConsumer++-- | Parse a number/probability+float :: Parser Prob+float = lexeme L.float++-- | Make sure definition blocks start un-indented+nonIndented = L.nonIndented spaceConsumer++--indentGuard = L.indentGuard spaceConsumer++-- | Parse between quotes+quote :: Parser a -> Parser a+quote = between .$ (symbol "\"")++-- | Parse a keyword+keyword :: String -> Parser String+keyword str = (pure <$> char ':') <> (symbol str) <?> "keyword"++-- | Parse the `define` keyword.+define :: Parser ()+define = (void $ nonIndented (keyword "define"))+    <?> "define block"+    --make them more similar/reuse code here!!++-- | Parse the `:return` keyword.+main :: Parser ()+main = (void $ nonIndented (keyword "return"))+    <?> "return block"++-- | Parse a template name (what follows a `:define` or `return` block)+name :: Parser String+name = lexeme (some letterChar) <?> "template name"++-- | Parse template into a `PreTok` of referents and strings+preStr :: Parser PreTok+preStr = (fmap (Name . T.pack) name) <|>+    do {+    s <- quote (many $ noneOf ("\"\'" :: String)) ;+    pure $ PreTok . T.pack $ s+    } +    <?> "string or function name"++-- | Parse a probability/corresponding template+pair :: Parser (Prob, [PreTok])+pair = do+    --indentGuard+    p <- float+    str <- some $ preStr+    pure (p, str)++-- | Parse a `define` block+definition :: Parser (Key, [(Prob, [PreTok])])+definition = do+    define+    str <- name+    val <- some pair+    --linebreak+    pure (T.pack str, val)++-- | Parse the `:return` block+final :: Parser [(Prob, [PreTok])]+final = do+    main+    val <- some pair+    pure val++-- | Parse the program in terms of `PreTok` and the `Key`s to link them.+program :: Parser [(Key, [(Prob, [PreTok])])]+program = sortKeys . checkSemantics <$> do+    p <- many (try definition <|> ((,) "Template" <$> final))+    pure p++-- | Parse text as a token + context (aka a reader monad with all the other functions)+parseTokM :: Parser (Context RandTok)+parseTokM = fmap build program++-- | Parse text as a token+--+-- > f <- readFile "template.mad"+-- > parseTok f+parseTok :: T.Text -> Either (ParseError Char Dec) RandTok+parseTok f = snd . head . (filter (\(i,j) -> i == "Template")) . (flip execState []) <$> runParser parseTokM "" f
+ src/Text/Madlibs/Ana/ParseUtils.hs view
@@ -0,0 +1,51 @@+-- | Helper functions to sort out parsing+module Text.Madlibs.Ana.ParseUtils where++import Text.Madlibs.Internal.Types+import Text.Madlibs.Internal.Utils+import Text.Madlibs.Cata.SemErr+import Data.List+import qualified Data.Text as T+import Control.Monad.State+import Data.Foldable+import Control.Exception++--consider moving Ana.ParseUtils to Cata.Sorting++-- | Convert the stuff after the number to a `RandTok`+concatTok :: T.Text -> Context [PreTok] -> Context RandTok+concatTok param pretoks = do+    ctx <- get+    let unList (List a) = a+    let toRand (Name str) = List . snd . (head' str param) . (filter ((== str) . fst)). (map (\(i,j) -> (i, unList j))) $ ctx+        toRand (PreTok txt) = Value txt+    fold . (map toRand) <$> pretoks++-- | 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 <- sequence $ map (\(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)++-- | Sort the keys that we have parsed so that dependencies are in the correct places+sortKeys :: [(Key, [(Prob, [PreTok])])] -> [(Key, [(Prob, [PreTok])])]+sortKeys = sortBy orderKeys++-- | 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+    | any (\pair -> any (T.isInfixOf key1) (map unTok . snd $ pair)) l1 = LT+    | any (\pair -> any (T.isInfixOf key2) (map unTok . snd $ pair)) l1 = GT+    | otherwise = EQ
+ src/Text/Madlibs/Cata/Run.hs view
@@ -0,0 +1,21 @@+-- | Module containing functions to get `Text` from `RandTok`+module Text.Madlibs.Cata.Run where++import Text.Madlibs.Internal.Types+import Text.Madlibs.Internal.Utils+import qualified Data.Text as T+import System.Random.MWC++-- | Generate randomized text from a `RandTok`+run :: RandTok -> IO T.Text+run tok@(List rs) = do+    value <- (withSystemRandom . asGenST $ \gen -> uniform gen)+    let ret = ((snd . head) . filter ((>=value) . fst)) $ mkCdf tok+    case ret of+        (Value txt) -> pure txt+        tok@(List rs) -> run tok+run (Value txt) = pure txt++-- | Helper function to compute the cdf when we have a pdf+mkCdf :: RandTok -> [(Double, RandTok)]+mkCdf (List rs) = zip (cdf . (map fst) $ rs) (map snd rs)
+ src/Text/Madlibs/Cata/SemErr.hs view
@@ -0,0 +1,62 @@+-- | Module defining the SemErr data type+module Text.Madlibs.Cata.SemErr where++import Text.Madlibs.Internal.Types+import Data.Typeable+import Text.PrettyPrint.ANSI.Leijen+import Control.Exception+import qualified Data.Text as T++-- | Datatype for a semantic error+data SemanticError = OverloadedReturns | CircularFunctionCalls T.Text T.Text | ProbSum T.Text+    deriving (Typeable)++--also consider overloading parseError tbqh+-- | display a `SemanticError` nicely with coloration & whatnot+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 (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++-- | Constant to start `SemanticError`s+semErrStart :: Doc+semErrStart = dullred (text "\n  Semantic Error: ")++-- | Convert a `Text` to a `Doc` for use with a pretty-printer+text' :: T.Text -> Doc+text' = text . T.unpack++-- | derived exception instance+instance Exception SemanticError++-- | big semantics checker that sequences stuff+checkSemantics :: [(Key, [(Prob, [PreTok])])] -> [(Key, [(Prob, [PreTok])])]+checkSemantics = foldr (.) id [ checkProb+                              , checkReturn ]++-- | 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))+--potentially consider throwing mult. errors at once obvi++-- | helper to filter out stuff that doesn't+sumProb :: [(Prob, [PreTok])] -> Bool+sumProb = ((==1) . sum . (map fst))+--check for approximation too++-- | Take the head of the list, or throw the appropriate error given which functions we are trying to call.+head' :: T.Text -> T.Text -> [a] -> a+head' _ _ (x:xs) = x+head' f1 f2 _ = throw (CircularFunctionCalls f1 f2)++-- | checker to verify there is at most one `:return` statement+checkReturn :: [(Key, [(Prob, [PreTok])])] -> [(Key, [(Prob, [PreTok])])]+checkReturn keys+    | singleReturn keys = keys+    | otherwise = throw OverloadedReturns++-- | Checks that we have at most one `:return` template in the file+singleReturn :: [(Key, [(Prob, [PreTok])])] -> Bool+singleReturn = singleton . (filter ((=="Template") . fst))+    where singleton = not . null
+ src/Text/Madlibs/Exec/Main.hs view
@@ -0,0 +1,60 @@+{-# LANGUAGE TemplateHaskell #-}+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE TypeOperators #-}++-- | Provides `madlang` executable+module Text.Madlibs.Exec.Main where++import Text.Madlibs.Cata.Run+import Text.Madlibs.Ana.Parse+import Text.Madlibs.Internal.Types+import Text.Madlibs.Internal.Utils+import qualified Data.Text as T+import qualified Data.Text.IO as TIO+import Text.Megaparsec+import Options.Generic++-- | datatype for the program+data Program = Program { input :: FilePath <?> "filepath to template"+                       , debug :: Bool <?> "whether to display parsed RandTok" --also: display crontab in that case?+                       , rep :: Maybe Int <?> "How many times to repeat"+                       } deriving (Generic)++-- | Generated automatically by optparse-generic.+instance ParseRecord Program where++-- | Main program action+exec :: IO ()+exec = do+    x <- getRecord "Text.Madlibs templating DSL"+    case unHelpful . rep $ x of+        (Just n) -> sequence_ . (take n) . repeat $ template x+        Nothing -> template x++-- | given a parsed record perform the appropriate IO action+template :: Program -> IO ()+template rec = do+    let filepath = unHelpful . input $ rec+    parsed <- parseFile filepath+    runFile filepath >>= TIO.putStrLn+    if unHelpful . debug $ rec then+        print parsed+    else+        pure ()++-- | Generate randomized text from a template+templateGen :: T.Text -> Either (ParseError Char Dec) (IO T.Text)+templateGen txt = run <$> parseTok txt++-- | Generate randomized text from a file conatining a template+runFile :: FilePath -> IO T.Text+runFile filepath = do+    txt <- readFile' filepath+    either (pure . parseErrorPretty') (>>= (pure . show')) (templateGen txt)++-- | Parse a template file into the `RandTok` data type+parseFile :: FilePath -> IO (Either (ParseError Char Dec) RandTok)+parseFile filepath = do+    txt <- readFile' filepath+    let val = parseTok txt+    pure val
+ src/Text/Madlibs/Internal/Types.hs view
@@ -0,0 +1,43 @@+{-# LANGUAGE TypeSynonymInstances #-}+{-# LANGUAGE FlexibleInstances #-}++-- | Module with the type of a random token+module Text.Madlibs.Internal.Types where++import qualified Data.Text as T+import Control.Monad.State+import Data.Functor.Identity+import Control.Lens hiding (List, Context)+import Data.Function++-- | datatype for a double representing a probability+type Prob = Double++-- | dataype for a key aka token name+type Key = T.Text++-- | Pretoken, aka token as first read in+data PreTok = Name T.Text | PreTok T.Text +    deriving (Show)++-- | datatype for a token returning a random string+data RandTok = List [(Prob, RandTok)] | Value T.Text +    deriving (Show, Eq)++-- | 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")])+-- > (List [(0.5,"Hello you"), (0.5, "Hello me")])+instance Monoid RandTok where+    mempty = Value ""+    mappend (Value v1) (Value v2) = Value (T.append v1 v2)+    mappend (List l1) v@(Value v1) = List $ map (over (_2) (`mappend` v)) l1 +    mappend v@(Value v2) (List l2) = List $ map (over (_2) (`mappend` v)) l2+    mappend l@(List l1) (List l2) = List $ [ (p, l `mappend` tok) | (p,tok) <- l2 ]++-- | State monad providing context, i.e. function we've already called before+type Context a = State [(Key, RandTok)] a++-- | Compare inside the state monad using only the underlying objects+instance (Eq a) => Eq (Context a) where+    (==) a b = (on (==) (flip evalState [])) a b
+ src/Text/Madlibs/Internal/Utils.hs view
@@ -0,0 +1,44 @@+{-# LANGUAGE TypeSynonymInstances #-}+{-# LANGUAGE FlexibleInstances #-}++-- | Internal utils to help out elsewhere+module Text.Madlibs.Internal.Utils where++import Text.Madlibs.Internal.Types+import Text.Megaparsec.Text+import Text.Megaparsec.Error+import qualified Data.Text as T+import System.IO.Unsafe++-- | Function to apply a value on both arguments, e.g.+--+-- > between .$ (symbol "\'")+(.$) :: (a -> a -> b) -> a -> b+(.$) f x = f x x++-- | Add a PR for this? Could be useful in Megaparsec idk+-- Allows us to use monoidal addition on parsers+instance (Monoid a) => Monoid (Parser a) where+    mempty = pure mempty+    mappend x y = mappend <$> x <*> y++-- | Helper function for creating a cdf from a pdf+cdf :: [Prob] -> [Prob]+cdf = (drop 2) . (scanl (+) 0) . ((:) 0)++-- | Show as a T.Text+show' :: (Show a) => a -> T.Text+show' = (T.drop 1) . T.init . T.pack . show++-- | Pretty-print a ParseError+parseErrorPretty' :: ParseError Char Dec -> T.Text+parseErrorPretty' = T.pack . parseErrorPretty++-- | Strip a pre-token's name+unTok :: PreTok -> T.Text+unTok (PreTok txt) = ""+unTok (Name txt) = txt++-- | Read a file in as a `Text`+readFile' :: FilePath -> IO T.Text+readFile' = (fmap T.pack) . readFile
+ test/Spec.hs view
@@ -0,0 +1,72 @@+{-# LANGUAGE OverloadedStrings #-}++import Test.Hspec+import Test.Hspec.Megaparsec+import Text.Madlibs+import Text.Megaparsec+import Control.Monad.IO.Class+import Control.Monad.State+import Data.Function+import Control.Exception+import qualified Data.Text as T+import System.IO.Unsafe++main :: IO ()+main = hspec $ do+    describe "parseTok" $ do+        it "parses a .mad string" $ do+            parseTok madFile `shouldParse` (List [(1.0,List [(0.5,Value "heads"),(0.5,Value "tails")])])+        it "fails when quotes aren't closed" $ do+            parseTok `shouldFailOn` madFileFailure+        it "parses when functions are out of order" $ do+            parseTok `shouldSucceedOn` madComplexFile+        it "returns a correct string from the template when evaluating a token" $ do+            (testIO . run) exampleTok `shouldSatisfy` (\a -> on (||) (a ==) "heads" "tails")+        it "throws exception when two `:return`s are declared" $ do+            (parseTok `shouldFailOn` semErrFile) `shouldThrow` semErr+            --this is still behaving weirdly but I don't care+            --also we need a parse error one but that shouldn't be too hard idk++semErr :: Selector SemanticError+semErr = const True++exampleTok :: RandTok+exampleTok = List [(1.0,List [(0.5,Value "heads"),(0.5,Value "tails")])]++madFile :: T.Text+madFile = ":define something\n    0.5 \"heads\"\n    0.5 \"tails\"\n:return\n    1.0 something"++madFileFailure :: T.Text+madFileFailure = ":define something\+\    0.5 \"heads\"\+\    0.5 \"tails\+\:return\+\    1.0 something"++madComplexFile :: T.Text+madComplexFile = ":define person\+\    0.7 \"I will \"\+\    0.3 \"You will \"\+\:define goodfortune\+\    0.1 person \"make nice things happen today\"\+\    0.9 \"nice things will happen today\"\+\:define fortune\+\    0.5 \"drink a boatload of milk\"\+\    0.5 \"get heckin angry\"\+\:return\+\    0.8 person fortune\+\    0.2 goodfortune"++semErrFile :: T.Text+semErrFile = ":define something\+\    0.5 \"heads\"\+\    0.5 \"tails\"\+\:return\+\    1.0 something\+\:return\+\    0.5 something\+\    0.5 \"it doesn't matter b/c this is gonna blow up in our faces anyways\""++-- | for the testing framework; to +testIO :: IO a -> a+testIO = unsafePerformIO