hwk 0.5 → 0.6
raw patch · 8 files changed
+368/−270 lines, 8 files
Files
- ChangeLog.md +5/−0
- Main.hs +0/−264
- README.md +11/−3
- hwk.cabal +7/−3
- src/Common.hs +14/−0
- src/Hwk/IO.hs +46/−0
- src/Hwk/Types.hs +6/−0
- src/Main.hs +279/−0
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  -<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