packages feed

hwk 0.5 → 0.6

raw patch · 8 files changed

+368/−270 lines, 8 files

Files

ChangeLog.md view
@@ -1,5 +1,10 @@ # Version history for hwk +## 0.6 (2021-06-14)+- add --words mode: applies function to list of words in each line+- experimental shell mode+- initial config fixes+ ## 0.5 (2020-10-13) - file arguments can now provide input - experimental --run mode to execute IO
− Main.hs
@@ -1,264 +0,0 @@-{-# LANGUAGE CPP #-}--#if !MIN_VERSION_simple_cmd_args(0,1,4)-import Control.Applicative (-#if !MIN_VERSION_simple_cmd_args(0,1,3)-    (<|>),-#endif-    many)-#endif-import Control.Monad.Extra-import qualified Data.List.Extra as L-import Data.Version (showVersion)-import Language.Haskell.Interpreter-import SimpleCmdArgs-import System.Directory-import System.FilePath-import System.IO (hPutStrLn, stderr)--import Paths_hwk (getDataDir, version)--data HwkMode = DefaultMode | WholeMode | LineMode | TypeMode | EvalMode | RunMode-  deriving Eq--main :: IO ()-main = do-  userdir <- getXdgDirectory XdgConfig "hwk"-  simpleCmdArgs (Just version) "A Haskell awk/sed like tool"-    "Simple shell text processing with Haskell" $-    runExpr userdir <$> modeOpt <*> cfgdirOpt userdir <*> strArg "FUNCTION" <*> many (strArg "FILE...")-  where-    modeOpt :: Parser HwkMode-    modeOpt =-      flagWith' LineMode 'l' "line" "Apply function to each line" <|>-      flagWith' WholeMode 'a' "all" "Apply function once to the whole input" <|>-      flagWith' TypeMode 't' "typecheck" "Print out the type of the given function" <|>-      flagWith' EvalMode 'e' "eval" "Evaluate a Haskell expression" <|>-      flagWith DefaultMode RunMode 'r' "run" "Run Haskell IO"--    cfgdirOpt :: String -> Parser (Maybe FilePath)-    cfgdirOpt dir =-      optional (normalise <$> strOptionWith 'c' "config-dir" "DIR" ("Override the config dir [default:" ++ dir ++ "]"))--runExpr :: FilePath -> HwkMode -> Maybe FilePath -> String -> [FilePath] -> IO ()-runExpr userdir mode mcfgdir stmt files = do-  mapM_ checkFileExists files-  cfgdir <--    case mcfgdir of-      Just dir -> do-        unlessM (doesDirectoryExist dir) $-          error' $ dir ++ ": directory not found"-        return dir-      Nothing -> do-        let usercfg = userdir </> "Hwk.hs"-        datadir <- getDataDir-        let versionCfg = usercfg ++ "-" ++ showVersion version-        unlessM (doesFileExist versionCfg) $-          copyFile (datadir </> "Hwk.hs") versionCfg-        unlessM (doesFileExist usercfg) $ do-          copyFile (datadir </> "Hwk.hs") usercfg-          warn $ usercfg ++ " created"-        return userdir-  runInterpreter (runHint cfgdir) >>=-    either (putStrLn . errorString) return-  where-    runHint :: FilePath -> Interpreter ()-    runHint cfgdir = do-      -- could ignore this for eval or typecheck-      set [searchPath := [cfgdir], languageExtensions := [TypeApplications]]-      loadModules ["Hwk"]-      let setHwkImports ms = setImports (L.nub (ms ++ ["Hwk"]))-      setHwkImports ["Prelude"]-      imports <- do-        haveModules <- typeChecks "userModules"-        if haveModules-          then interpret "userModules" infer-          else return ["Prelude", "Data.List"]-      setHwkImports imports-      let inputs = if null files then ["-"] else files-      case mode of-        DefaultMode ->-          withInputFiles inputs (mapInputList stmt . lines)-        LineMode ->-          withInputFiles inputs (mapEachLine stmt . lines)-        WholeMode -> do-          withInputFiles inputs (applyToInput stmt . removeTrailingNewline)-        -- FIXME take or warn about args-        TypeMode -> typeOfExpr stmt-        EvalMode -> evalExpr stmt-        RunMode -> execExpr stmt-      where-        withInputFiles :: [FilePath] -> (String -> Interpreter ())-                       -> Interpreter ()-        withInputFiles inputs interp = do-          forM_ inputs $ \ file ->-            liftIO (if file == "-" then getContents else readFile file) >>=-            interp--        removeTrailingNewline :: String -> String-        removeTrailingNewline "" = ""-        removeTrailingNewline s =-          if last s == '\n'-          then init s-          else s--    checkFileExists :: FilePath -> IO ()-    checkFileExists "-" = return ()-    checkFileExists file = do-      unlessM (doesFileExist file) $-        error' $ "file not found: " ++ file--    errorString :: InterpreterError -> String-    errorString (WontCompile es) =-      unlines $ "ERROR: Won't compile:" : map errMsg es-    errorString e = show e--cleanupType :: String -> String-cleanupType = L.replace "FilePath" "String" . L.replace "[Char]" "String"---- fn $ lines input-mapInputList :: String -> [String] -> Interpreter ()-mapInputList stmt inputs = do-  typ <- resultTypeOfApplied stmt "[String]"-  case cleanupType typ of-    "String" -> do-      fn <- interpret stmt (as :: [String] -> String)-      liftIO $ putStrLn (fn inputs)-    "[String]" -> do-      fn <- interpret stmt (as :: [String] -> [String])-      liftIO $ mapM_ putStrLn (fn inputs)-    "[[String]]" -> do-      fn <- interpret stmt (as :: [String] -> [[String]])-      liftIO $ mapM_ (putStrLn . unwords) (fn inputs)-    "Int" -> do-      fn <- interpret stmt (as :: [String] -> Int)-      liftIO $ print (fn inputs)-    "[Int]" -> do-      fn <- interpret stmt (as :: [String] -> [Int])-      liftIO $ mapM_ print (fn inputs)-    "[[Int]]" -> do-      fn <- interpret stmt (as :: [String] -> [[Int]])-      liftIO $ mapM_ (putStrLn . unwords . map show) (fn inputs)-    _ -> do-      liftIO $ warn typ-      fn <- interpret stmt (as :: [String] -> [String])-      liftIO $ mapM_ putStrLn (fn inputs)---- map fn $ lines input-mapEachLine :: String -> [String] -> Interpreter ()-mapEachLine stmt inputs = do-  typ <- resultTypeOfApplied stmt "String"-  case cleanupType typ of-    "String" -> do-      fn <- interpret stmt (as :: String -> String)-      liftIO $ mapM_ (putStrLn . fn) inputs-    "[String]" -> do-      fn <- interpret stmt (as :: String -> [String])-      liftIO $ mapM_ (putStrLn . unwords . fn) inputs-    "[[String]]" -> do-      fn <- interpret stmt (as :: String -> [[String]])-      liftIO $ mapM_ (putStrLn . L.intercalate "\t" . map unwords . fn) inputs-    "Int" -> do-      fn <- interpret stmt (as :: String -> Int)-      liftIO $ mapM_ (print . fn) inputs-    "[Int]" -> do-      fn <- interpret stmt (as :: String -> [Int])-      liftIO $ mapM_ (putStrLn . unwords . map show . fn) inputs-    "[[Int]]" -> do-      fn <- interpret stmt (as :: String -> [[Int]])-      liftIO $ mapM_ (putStrLn . L.intercalate "\t" . map (unwords . map show) .fn) inputs-    _ -> do-      liftIO $ warn typ-      fn <- interpret stmt (as :: String -> String)-      liftIO $ mapM_ (putStrLn . fn) inputs---- fn input-applyToInput :: String -> String -> Interpreter ()-applyToInput stmt input = do-  typ <- resultTypeOfApplied stmt "String"-  case cleanupType typ of-    "String" -> do-      fn <- interpret stmt (as :: String -> String)-      liftIO $ putStrLn (fn input)-    "[String]" -> do-      fn <- interpret stmt (as :: String -> [String])-      liftIO $ mapM_ putStrLn (fn input)-    "[[String]]" -> do-      fn <- interpret stmt (as :: String -> [[String]])-      liftIO $ mapM_ (putStrLn . unwords) (fn input)-    "Int" -> do-      fn <- interpret stmt (as :: String -> Int)-      liftIO $ print (fn input)-    "[Int]" -> do-      fn <- interpret stmt (as :: String -> [Int])-      liftIO $ mapM_ print (fn input)-    "[[Int]]" -> do-      fn <- interpret stmt (as :: String -> [[Int]])-      liftIO $ mapM_ (putStrLn . unwords . map show) (fn input)-    _ -> do-      liftIO $ warn typ-      fn <- interpret stmt (as :: String -> [String])-      liftIO $ mapM_ putStrLn (fn input)--typeOfExpr :: String -> Interpreter ()-typeOfExpr stmt = do-#if MIN_VERSION_hint(0,8,0)-  etypchk <- typeChecksWithDetails stmt-  case etypchk of-    Left err -> liftIO $ mapM_ (putStrLn . errMsg) err-    Right typ -> liftIO $ putStrLn (cleanupType typ)-#else-  typeOf stmt >>= liftIO . putStrLn . cleanupType-#endif--evalExpr :: String -> Interpreter ()-evalExpr stmt = do-  typ <- typeOf stmt-  case cleanupType typ of-    "String" -> do-      interpret stmt "a String" >>= liftIO . putStrLn-    "[String]" -> do-      interpret stmt ["a String"] >>= liftIO . mapM_ putStrLn-    "[[String]]" -> do-      interpret stmt [["a String"]] >>= liftIO . mapM_ (putStrLn . unwords)-    "Int" -> do-      interpret stmt (1 :: Int) >>= liftIO . print-    "[Int]" -> do-      interpret stmt [1 :: Int] >>= liftIO . mapM_ print-    "[[Int]]" -> do-      interpret stmt [[1 :: Int]] >>= liftIO . mapM_ (putStrLn . unwords . map show)-    _ ->-      if " -> " `L.isInfixOf` typ-        then liftIO $ putStrLn typ-        else-        -- FIXME option to display type-        eval stmt >>= liftIO . putStrLn---- FIXME add --safe?-execExpr :: String -> Interpreter ()-execExpr stmt = do-  typ <- typeOf stmt-  case cleanupType typ of-    "IO ()" -> runStmt stmt-    "IO String" ->-      runStmt $ stmt ++ ">>= putStrLn"-    "IO [String]" ->-      runStmt $ stmt ++ ">>= mapM_ putStrLn"-    "IO [[String]]" ->-      runStmt $ stmt ++ ">>= mapM_ (putStrLn . unwords)"-    _ -> liftIO $ warn typ--resultTypeOfApplied :: MonadInterpreter m => String -> String -> m String-resultTypeOfApplied expr typ =-  L.dropPrefix (typ ++ " -> ") <$> typeOf (expr ++ " . (id  @" ++ typ ++ ")")--warn :: String -> IO ()-warn = hPutStrLn stderr---- from simple-cmd-error' :: String -> a-#if (defined(MIN_VERSION_base) && MIN_VERSION_base(4,9,0))-error' = errorWithoutStackTrace-#else-error' = error-#endif
README.md view
@@ -1,10 +1,10 @@ # hwk ![MIT licensed](https://img.shields.io/badge/license-MIT-blue.svg) -<img align="right" alt="hwk" src="hwk.png" />- **hwk** (pronounced "hawk") is a simple Haskell-based commandline text processing tool, somewhat similar to tools like *awk*, *grep*, *sed*. `hwk` applies composed pure Haskell functions to a list of lines of input, enabling text processing without having to remember an obscure DSL or awkward cli options. This tool can also help to encourage people to think functionally. +<img align="right" alt="[hawk image]" src="hwk.png" />+ hwk was originally written by Lukas Martinelli in 2016-2017: see the [original README file](README.md.orig). @@ -126,10 +126,18 @@ - `hwk` use the hint library to apply haskell functions to input. - By default it splits the input to a list of lines and applies the function to them - Use `-a` or `--all` to apply a function to all the input,-  or `-l`/`--line` to map the function on each line separately.+  or `-l`/`--line` to map the function on each line separately,+  or `-w`/`--words` to map the function on each line of words, - If you pass file arguments, their contents will be read and passed to the function. - You can also typecheck the function or an expression with `-t`/`--typecheck`,   evaluate an expr with `-e`/`--eval`, or `-r`/`--run` an IO statement.++Note the equivalences:+- `hwk 'f . unlines'` == `hwk -a f`+- `hwk 'map f'` == `hwk -l f`+- `hwk -a 'f . lines'` == `hwk f`+- `hwk -l 'f . words'` == `hwk -w f`+- `hwk 'map (f . words)'` == `hwk -w f`  ## Supported return types 
hwk.cabal view
@@ -1,5 +1,5 @@ name:                hwk-version:             0.5+version:             0.6 synopsis:            Commandline text processing with Haskell functions description:             A commandline tool for text processing with Haskell functions,@@ -12,7 +12,7 @@ author:              Lukas Martinelli maintainer:          Jens Petersen <juhpetersen@gmail.com> copyright:           2016-2017 Lukas Martinelli,-                     2020 Jens Petersen+                     2020-2021 Jens Petersen category:            Development build-type:          Simple data-files:          Hwk.hs@@ -27,8 +27,12 @@  executable hwk   main-is:             Main.hs-  other-modules:       Paths_hwk+  hs-source-dirs:      src   autogen-modules:     Paths_hwk+  other-modules:       Paths_hwk+                       Common+                       Hwk.IO+                       Hwk.Types   build-depends:       base <5,                        directory >= 1.2.3.0,                        extra,
+ src/Common.hs view
@@ -0,0 +1,14 @@+module Common (+  setNoBuffering,+  warn+  )+where++import System.IO++warn :: String -> IO ()+warn = hPutStrLn stderr++setNoBuffering :: IO ()+setNoBuffering =+  hSetBuffering stdout NoBuffering
+ src/Hwk/IO.hs view
@@ -0,0 +1,46 @@+module Hwk.IO (execExpr, shellSession)+where++import Language.Haskell.Interpreter++import Common+import Hwk.Types++-- FIXME add --safe?+execExpr :: String -> Interpreter ()+execExpr stmt = do+  typ <- typeOf stmt+  case cleanupType typ of+    "IO ()" -> runStmt stmt+    "IO String" ->+      runStmt $ stmt ++ ">>= putStrLn"+    "IO [String]" ->+      runStmt $ stmt ++ ">>= mapM_ putStrLn"+    "IO [[String]]" ->+      runStmt $ stmt ++ ">>= mapM_ (putStrLn . unwords)"+    _ -> liftIO $ warn typ++shellSession :: Interpreter ()+shellSession = do+  liftIO $ putStr ">= "+  stmt <- liftIO getLine+  if null (words stmt) then shellSession+    else do+    etypchk <- typeChecksWithDetails stmt+    case etypchk of+      Left err -> do+        liftIO $ mapM_ (putStrLn . errMsg) err+        runStmt stmt+        shellSession+      Right typ -> do+        liftIO $ putStrLn $ ":: " ++ cleanupType typ+        case cleanupType typ of+          "IO ()" -> runStmt stmt+          "IO String" ->+            runStmt $ stmt ++ ">>= putStrLn"+          "IO [String]" ->+            runStmt $ stmt ++ ">>= mapM_ putStrLn"+          "IO [[String]]" ->+            runStmt $ stmt ++ ">>= mapM_ (putStrLn . unwords)"+          _ -> eval stmt >>= liftIO . putStrLn+        shellSession
+ src/Hwk/Types.hs view
@@ -0,0 +1,6 @@+module Hwk.Types (cleanupType) where++import qualified Data.List.Extra as L++cleanupType :: String -> String+cleanupType = L.replace "FilePath" "String" . L.replace "[Char]" "String"
+ src/Main.hs view
@@ -0,0 +1,279 @@+{-# LANGUAGE CPP #-}++#if !MIN_VERSION_simple_cmd_args(0,1,4)+import Control.Applicative (+#if !MIN_VERSION_simple_cmd_args(0,1,3)+    (<|>),+#endif+    many)+#endif+import Control.Monad.Extra+import qualified Data.List.Extra as L+import Data.Version (showVersion)+import Language.Haskell.Interpreter+import SimpleCmdArgs+import System.Directory+import System.FilePath++import Common+import Hwk.IO+import Hwk.Types+import Paths_hwk (getDataDir, version)++data HwkMode = DefaultMode | LineMode | WordsMode | WholeMode | TypeMode | EvalMode | RunMode | ShellMode+  deriving Eq++main :: IO ()+main = do+  userdir <- getXdgDirectory XdgConfig "hwk"+  simpleCmdArgs (Just version) "A Haskell awk/sed like tool"+    "Simple shell text processing with Haskell" $+    runExpr userdir <$> modeOpt <*> cfgdirOpt userdir <*> strArg "FUNCTION" <*> many (strArg "FILE...")+  where+    modeOpt :: Parser HwkMode+    modeOpt =+      flagWith' LineMode 'l' "line" "Apply function to each line" <|>+      flagWith' WordsMode 'w' "words" "Apply function to list of words per line" <|>+      flagWith' WholeMode 'a' "all" "Apply function once to the whole input" <|>+      flagWith' TypeMode 't' "typecheck" "Print out the type of the given function" <|>+      flagWith' EvalMode 'e' "eval" "Evaluate a Haskell expression" <|>+      flagWith' RunMode 'r' "run" "Run Haskell IO" <|>+      flagWith DefaultMode ShellMode 's' "shell" "Haskell Shell"++    cfgdirOpt :: String -> Parser (Maybe FilePath)+    cfgdirOpt dir =+      optional (normalise <$> strOptionWith 'c' "config-dir" "DIR" ("Override the config dir [default:" ++ dir ++ "]"))++runExpr :: FilePath -> HwkMode -> Maybe FilePath -> String -> [FilePath] -> IO ()+runExpr userdir mode mcfgdir stmt files = do+  mapM_ checkFileExists files+  cfgdir <-+    case mcfgdir of+      Just dir -> do+        unlessM (doesDirectoryExist dir) $+          error' $ dir ++ ": directory not found"+        return dir+      Nothing -> do+        let usercfg = userdir </> "Hwk.hs"+        datadir <- getDataDir+        let datafile = datadir </> "Hwk.hs"+        unlessM (doesFileExist datafile) $+          error' $ "default config file does not exist: " ++ datafile+        createDirectoryIfMissing True userdir+        let versionCfg = usercfg ++ "-" ++ showVersion version+        unlessM (doesFileExist versionCfg) $+          copyFile datafile versionCfg+        unlessM (doesFileExist usercfg) $ do+          copyFile datafile usercfg+          warn $ usercfg ++ " created"+        return userdir+  runInterpreter (runHint cfgdir) >>=+    either (putStrLn . errorString) return+  where+    runHint :: FilePath -> Interpreter ()+    runHint cfgdir = do+      -- could ignore this for eval or typecheck+      set [searchPath := [cfgdir], languageExtensions := [TypeApplications]]+      loadModules ["Hwk"]+      let setHwkImports ms = setImports (L.nub (ms ++ ["Hwk"]))+      setHwkImports ["Prelude"]+      imports <- do+        haveModules <- typeChecks "userModules"+        if haveModules+          then interpret "userModules" infer+          else return ["Prelude", "Data.List"]+      setHwkImports imports+      let inputs = if null files then ["-"] else files+      case mode of+        DefaultMode ->+          withInputFiles inputs (mapInputList stmt . lines)+        LineMode ->+          withInputFiles inputs (mapEachLine stmt . lines)+        WordsMode ->+          withInputFiles inputs (mapWordsLine stmt . map words . lines)+        WholeMode -> do+          withInputFiles inputs (applyToInput stmt . removeTrailingNewline)+        -- FIXME take or warn about args+        TypeMode -> typeOfExpr stmt+        EvalMode -> evalExpr stmt+        RunMode -> execExpr stmt+        ShellMode -> liftIO setNoBuffering >> shellSession+      where+        withInputFiles :: [FilePath] -> (String -> Interpreter ())+                       -> Interpreter ()+        withInputFiles inputs interp = do+          forM_ inputs $ \ file ->+            liftIO (if file == "-" then getContents else readFile file) >>=+            interp++        removeTrailingNewline :: String -> String+        removeTrailingNewline "" = ""+        removeTrailingNewline s =+          if last s == '\n'+          then init s+          else s++    checkFileExists :: FilePath -> IO ()+    checkFileExists "-" = return ()+    checkFileExists file = do+      unlessM (doesFileExist file) $+        error' $ "file not found: " ++ file++    errorString :: InterpreterError -> String+    errorString (WontCompile es) =+      unlines $ "ERROR: Won't compile:" : map errMsg es+    errorString e = show e++-- fn $ lines input+mapInputList :: String -> [String] -> Interpreter ()+mapInputList stmt inputs = do+  typ <- resultTypeOfApplied stmt "[String]"+  case cleanupType typ of+    "String" -> do+      fn <- interpret stmt (as :: [String] -> String)+      liftIO $ putStrLn (fn inputs)+    "[String]" -> do+      fn <- interpret stmt (as :: [String] -> [String])+      liftIO $ mapM_ putStrLn (fn inputs)+    "[[String]]" -> do+      fn <- interpret stmt (as :: [String] -> [[String]])+      liftIO $ mapM_ (putStrLn . unwords) (fn inputs)+    "Int" -> do+      fn <- interpret stmt (as :: [String] -> Int)+      liftIO $ print (fn inputs)+    "[Int]" -> do+      fn <- interpret stmt (as :: [String] -> [Int])+      liftIO $ mapM_ print (fn inputs)+    "[[Int]]" -> do+      fn <- interpret stmt (as :: [String] -> [[Int]])+      liftIO $ mapM_ (putStrLn . unwords . map show) (fn inputs)+    _ -> do+      liftIO $ warn typ+      fn <- interpret stmt (as :: [String] -> [String])+      liftIO $ mapM_ putStrLn (fn inputs)++-- map fn $ lines input+mapEachLine :: String -> [String] -> Interpreter ()+mapEachLine stmt inputs = do+  typ <- resultTypeOfApplied stmt "String"+  case cleanupType typ of+    "String" -> do+      fn <- interpret stmt (as :: String -> String)+      liftIO $ mapM_ (putStrLn . fn) inputs+    "[String]" -> do+      fn <- interpret stmt (as :: String -> [String])+      liftIO $ mapM_ (putStrLn . unwords . fn) inputs+    "[[String]]" -> do+      fn <- interpret stmt (as :: String -> [[String]])+      liftIO $ mapM_ (putStrLn . L.intercalate "\t" . map unwords . fn) inputs+    "Int" -> do+      fn <- interpret stmt (as :: String -> Int)+      liftIO $ mapM_ (print . fn) inputs+    "[Int]" -> do+      fn <- interpret stmt (as :: String -> [Int])+      liftIO $ mapM_ (putStrLn . unwords . map show . fn) inputs+    "[[Int]]" -> do+      fn <- interpret stmt (as :: String -> [[Int]])+      liftIO $ mapM_ (putStrLn . L.intercalate "\t" . map (unwords . map show) .fn) inputs+    _ -> do+      liftIO $ warn typ+      fn <- interpret stmt (as :: String -> String)+      liftIO $ mapM_ (putStrLn . fn) inputs++-- map fn $ lines input+mapWordsLine :: String -> [[String]] -> Interpreter ()+mapWordsLine stmt inputs = do+  typ <- resultTypeOfApplied stmt "[String]"+  case cleanupType typ of+    "String" -> do+      fn <- interpret stmt (as :: [String] -> String)+      liftIO $ mapM_ (putStrLn . fn) inputs+    "[String]" -> do+      fn <- interpret stmt (as :: [String] -> [String])+      liftIO $ mapM_ (putStrLn . unwords . fn) inputs+    "[[String]]" -> do+      fn <- interpret stmt (as :: [String] -> [[String]])+      liftIO $ mapM_ (putStrLn . L.intercalate "\t" . map unwords . fn) inputs+    "Int" -> do+      fn <- interpret stmt (as :: [String] -> Int)+      liftIO $ mapM_ (print . fn) inputs+    "[Int]" -> do+      fn <- interpret stmt (as :: [String] -> [Int])+      liftIO $ mapM_ (putStrLn . unwords . map show . fn) inputs+    "[[Int]]" -> do+      fn <- interpret stmt (as :: [String] -> [[Int]])+      liftIO $ mapM_ (putStrLn . L.intercalate "\t" . map (unwords . map show) .fn) inputs+    _ -> do+      liftIO $ warn typ+      fn <- interpret stmt (as :: [String] -> [String])+      liftIO $ mapM_ (putStrLn . unwords . fn) inputs++-- fn input+applyToInput :: String -> String -> Interpreter ()+applyToInput stmt input = do+  typ <- resultTypeOfApplied stmt "String"+  case cleanupType typ of+    "String" -> do+      fn <- interpret stmt (as :: String -> String)+      liftIO $ putStrLn (fn input)+    "[String]" -> do+      fn <- interpret stmt (as :: String -> [String])+      liftIO $ mapM_ putStrLn (fn input)+    "[[String]]" -> do+      fn <- interpret stmt (as :: String -> [[String]])+      liftIO $ mapM_ (putStrLn . unwords) (fn input)+    "Int" -> do+      fn <- interpret stmt (as :: String -> Int)+      liftIO $ print (fn input)+    "[Int]" -> do+      fn <- interpret stmt (as :: String -> [Int])+      liftIO $ mapM_ print (fn input)+    "[[Int]]" -> do+      fn <- interpret stmt (as :: String -> [[Int]])+      liftIO $ mapM_ (putStrLn . unwords . map show) (fn input)+    _ -> do+      liftIO $ warn typ+      fn <- interpret stmt (as :: String -> [String])+      liftIO $ mapM_ putStrLn (fn input)++resultTypeOfApplied :: MonadInterpreter m => String -> String -> m String+resultTypeOfApplied expr typ =+  L.dropPrefix (typ ++ " -> ") <$> typeOf (expr ++ " . (id  @" ++ typ ++ ")")++typeOfExpr :: String -> Interpreter ()+typeOfExpr stmt = do+  etypchk <- typeChecksWithDetails stmt+  case etypchk of+    Left err -> liftIO $ mapM_ (putStrLn . errMsg) err+    Right typ -> liftIO $ putStrLn (cleanupType typ)++evalExpr :: String -> Interpreter ()+evalExpr stmt = do+  typ <- typeOf stmt+  case cleanupType typ of+    "String" -> do+      interpret stmt "a String" >>= liftIO . putStrLn+    "[String]" -> do+      interpret stmt ["a String"] >>= liftIO . mapM_ putStrLn+    "[[String]]" -> do+      interpret stmt [["a String"]] >>= liftIO . mapM_ (putStrLn . unwords)+    "Int" -> do+      interpret stmt (1 :: Int) >>= liftIO . print+    "[Int]" -> do+      interpret stmt [1 :: Int] >>= liftIO . mapM_ print+    "[[Int]]" -> do+      interpret stmt [[1 :: Int]] >>= liftIO . mapM_ (putStrLn . unwords . map show)+    _ ->+      if " -> " `L.isInfixOf` typ+        then liftIO $ putStrLn typ+        else+        -- FIXME option to display type+        eval stmt >>= liftIO . putStrLn++-- from simple-cmd+error' :: String -> a+#if (defined(MIN_VERSION_base) && MIN_VERSION_base(4,9,0))+error' = errorWithoutStackTrace+#else+error' = error+#endif