✨
This commit is contained in:
35
a4/a4.cabal
Normal file
35
a4/a4.cabal
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
cabal-version: 3.0
|
||||||
|
name: a4
|
||||||
|
version: 1.0.0.0
|
||||||
|
build-type: Simple
|
||||||
|
|
||||||
|
common common
|
||||||
|
default-language: Haskell2010
|
||||||
|
ghc-options: -Wall
|
||||||
|
|
||||||
|
library
|
||||||
|
import: common
|
||||||
|
hs-source-dirs: src
|
||||||
|
build-depends:
|
||||||
|
base
|
||||||
|
, tasty
|
||||||
|
, tasty-hunit
|
||||||
|
, process
|
||||||
|
, directory
|
||||||
|
exposed-modules:
|
||||||
|
APL.AST
|
||||||
|
APL.Eval
|
||||||
|
APL.InterpPure
|
||||||
|
APL.InterpIO
|
||||||
|
APL.Interp_Tests
|
||||||
|
APL.Monad
|
||||||
|
APL.Util
|
||||||
|
|
||||||
|
test-suite a4-test
|
||||||
|
import: common
|
||||||
|
type: exitcode-stdio-1.0
|
||||||
|
main-is: runtests.hs
|
||||||
|
build-depends:
|
||||||
|
base
|
||||||
|
, tasty
|
||||||
|
, a4
|
5
a4/runtests.hs
Normal file
5
a4/runtests.hs
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
import qualified APL.Interp_Tests
|
||||||
|
import Test.Tasty (defaultMain)
|
||||||
|
|
||||||
|
main :: IO ()
|
||||||
|
main = defaultMain APL.Interp_Tests.tests
|
27
a4/src/APL/AST.hs
Normal file
27
a4/src/APL/AST.hs
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
module APL.AST
|
||||||
|
( VName,
|
||||||
|
Exp (..),
|
||||||
|
)
|
||||||
|
where
|
||||||
|
|
||||||
|
type VName = String
|
||||||
|
|
||||||
|
data Exp
|
||||||
|
= CstInt Integer
|
||||||
|
| CstBool Bool
|
||||||
|
| Add Exp Exp
|
||||||
|
| Sub Exp Exp
|
||||||
|
| Mul Exp Exp
|
||||||
|
| Div Exp Exp
|
||||||
|
| Pow Exp Exp
|
||||||
|
| Eql Exp Exp
|
||||||
|
| If Exp Exp Exp
|
||||||
|
| Var VName
|
||||||
|
| Let VName Exp Exp
|
||||||
|
| Lambda VName Exp
|
||||||
|
| Apply Exp Exp
|
||||||
|
| TryCatch Exp Exp
|
||||||
|
| Print String Exp
|
||||||
|
| KvPut Exp Exp
|
||||||
|
| KvGet Exp
|
||||||
|
deriving (Eq, Show)
|
73
a4/src/APL/Eval.hs
Normal file
73
a4/src/APL/Eval.hs
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
module APL.Eval
|
||||||
|
( eval,
|
||||||
|
)
|
||||||
|
where
|
||||||
|
|
||||||
|
import APL.AST (Exp (..))
|
||||||
|
import APL.Monad
|
||||||
|
|
||||||
|
evalIntBinOp :: (Integer -> Integer -> EvalM Integer) -> Exp -> Exp -> EvalM Val
|
||||||
|
evalIntBinOp f e1 e2 = do
|
||||||
|
v1 <- eval e1
|
||||||
|
v2 <- eval e2
|
||||||
|
case (v1, v2) of
|
||||||
|
(ValInt x, ValInt y) -> ValInt <$> f x y
|
||||||
|
(_, _) -> failure "Non-integer operand"
|
||||||
|
|
||||||
|
evalIntBinOp' :: (Integer -> Integer -> Integer) -> Exp -> Exp -> EvalM Val
|
||||||
|
evalIntBinOp' f e1 e2 =
|
||||||
|
evalIntBinOp f' e1 e2
|
||||||
|
where
|
||||||
|
f' x y = pure $ f x y
|
||||||
|
|
||||||
|
-- Replace with your 'eval' from your solution to assignment 2.
|
||||||
|
eval :: Exp -> EvalM Val
|
||||||
|
eval (CstInt x) = pure $ ValInt x
|
||||||
|
eval (CstBool b) = pure $ ValBool b
|
||||||
|
eval (Var v) = do
|
||||||
|
env <- askEnv
|
||||||
|
case envLookup v env of
|
||||||
|
Just x -> pure x
|
||||||
|
Nothing -> failure $ "Unknown variable: " ++ v
|
||||||
|
eval (Add e1 e2) = evalIntBinOp' (+) e1 e2
|
||||||
|
eval (Sub e1 e2) = evalIntBinOp' (-) e1 e2
|
||||||
|
eval (Mul e1 e2) = evalIntBinOp' (*) e1 e2
|
||||||
|
eval (Div e1 e2) = evalIntBinOp checkedDiv e1 e2
|
||||||
|
where
|
||||||
|
checkedDiv _ 0 = failure "Division by zero"
|
||||||
|
checkedDiv x y = pure $ x `div` y
|
||||||
|
eval (Pow e1 e2) = evalIntBinOp checkedPow e1 e2
|
||||||
|
where
|
||||||
|
checkedPow x y =
|
||||||
|
if y < 0
|
||||||
|
then failure "Negative exponent"
|
||||||
|
else pure $ x ^ y
|
||||||
|
eval (Eql e1 e2) = do
|
||||||
|
v1 <- eval e1
|
||||||
|
v2 <- eval e2
|
||||||
|
case (v1, v2) of
|
||||||
|
(ValInt x, ValInt y) -> pure $ ValBool $ x == y
|
||||||
|
(ValBool x, ValBool y) -> pure $ ValBool $ x == y
|
||||||
|
(_, _) -> failure "Invalid operands to equality"
|
||||||
|
eval (If cond e1 e2) = do
|
||||||
|
cond' <- eval cond
|
||||||
|
case cond' of
|
||||||
|
ValBool True -> eval e1
|
||||||
|
ValBool False -> eval e2
|
||||||
|
_ -> failure "Non-boolean conditional."
|
||||||
|
eval (Let var e1 e2) = do
|
||||||
|
v1 <- eval e1
|
||||||
|
localEnv (envExtend var v1) $ eval e2
|
||||||
|
eval (Lambda var body) = do
|
||||||
|
env <- askEnv
|
||||||
|
pure $ ValFun env var body
|
||||||
|
eval (Apply e1 e2) = do
|
||||||
|
v1 <- eval e1
|
||||||
|
v2 <- eval e2
|
||||||
|
case (v1, v2) of
|
||||||
|
(ValFun f_env var body, arg) ->
|
||||||
|
localEnv (const $ envExtend var arg f_env) $ eval body
|
||||||
|
(_, _) ->
|
||||||
|
failure "Cannot apply non-function"
|
||||||
|
eval (TryCatch e1 e2) =
|
||||||
|
eval e1 `catch` eval e2
|
70
a4/src/APL/InterpIO.hs
Normal file
70
a4/src/APL/InterpIO.hs
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
module APL.InterpIO (runEvalIO) where
|
||||||
|
|
||||||
|
import APL.Monad
|
||||||
|
import APL.Util
|
||||||
|
import System.Directory (removeFile)
|
||||||
|
import System.IO (hFlush, readFile', stdout)
|
||||||
|
|
||||||
|
-- Converts a string into a value. Only 'ValInt's and 'ValBool' are supported.
|
||||||
|
readVal :: String -> Maybe Val
|
||||||
|
readVal = unserialize
|
||||||
|
|
||||||
|
-- 'prompt s' prints 's' to the console and then reads a line from stdin.
|
||||||
|
prompt :: String -> IO String
|
||||||
|
prompt s = do
|
||||||
|
putStr s
|
||||||
|
hFlush stdout
|
||||||
|
getLine
|
||||||
|
|
||||||
|
-- 'writeDB dbFile s' writes the 'State' 's' to the file 'db'.
|
||||||
|
writeDB :: FilePath -> State -> IO ()
|
||||||
|
writeDB db s =
|
||||||
|
writeFile db $ serialize s
|
||||||
|
|
||||||
|
-- 'readDB db' reads the database stored in 'db'.
|
||||||
|
readDB :: FilePath -> IO (Either Error State)
|
||||||
|
readDB db = do
|
||||||
|
ms <- readFile' db
|
||||||
|
case unserialize ms of
|
||||||
|
Just s -> pure $ pure s
|
||||||
|
Nothing -> pure $ Left "Invalid DB."
|
||||||
|
|
||||||
|
-- 'copyDB db1 db2' copies 'db1' to 'db2'.
|
||||||
|
copyDB :: FilePath -> FilePath -> IO ()
|
||||||
|
copyDB db db' = do
|
||||||
|
s <- readFile' db
|
||||||
|
writeFile db' s
|
||||||
|
|
||||||
|
-- Removes all key-value pairs from the database file.
|
||||||
|
clearDB :: IO ()
|
||||||
|
clearDB = writeFile dbFile ""
|
||||||
|
|
||||||
|
-- The name of the database file.
|
||||||
|
dbFile :: FilePath
|
||||||
|
dbFile = "db.txt"
|
||||||
|
|
||||||
|
-- Creates a fresh temporary database, passes it to a function returning an
|
||||||
|
-- IO-computation, executes the computation, deletes the temporary database, and
|
||||||
|
-- finally returns the result of the computation. The temporary database file is
|
||||||
|
-- guaranteed fresh and won't have a name conflict with any other files.
|
||||||
|
withTempDB :: (FilePath -> IO a) -> IO a
|
||||||
|
withTempDB m = do
|
||||||
|
tempDB <- newTempDB -- Create a new temp database file.
|
||||||
|
res <- m tempDB -- Run the computation with the new file.
|
||||||
|
removeFile tempDB -- Delete the temp database file.
|
||||||
|
pure res -- Return the result of the computation.
|
||||||
|
|
||||||
|
runEvalIO :: EvalM a -> IO (Either Error a)
|
||||||
|
runEvalIO evalm = do
|
||||||
|
clearDB
|
||||||
|
runEvalIO' envEmpty dbFile evalm
|
||||||
|
where
|
||||||
|
runEvalIO' :: Env -> FilePath -> EvalM a -> IO (Either Error a)
|
||||||
|
runEvalIO' _ _ (Pure x) = pure $ pure x
|
||||||
|
runEvalIO' r db (Free (ReadOp k)) = runEvalIO' r db $ k r
|
||||||
|
runEvalIO' r db (Free (StateGetOp k)) = error "TODO in Task 3"
|
||||||
|
runEvalIO' r db (Free (StatePutOp s m)) = error "TODO in Task 3"
|
||||||
|
runEvalIO' r db (Free (PrintOp p m)) = do
|
||||||
|
putStrLn p
|
||||||
|
runEvalIO' r db m
|
||||||
|
runEvalIO' _ _ (Free (ErrorOp e)) = pure $ Left e
|
16
a4/src/APL/InterpPure.hs
Normal file
16
a4/src/APL/InterpPure.hs
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
module APL.InterpPure (runEval) where
|
||||||
|
|
||||||
|
import APL.Monad
|
||||||
|
|
||||||
|
runEval :: EvalM a -> ([String], Either Error a)
|
||||||
|
runEval = runEval' envEmpty stateInitial
|
||||||
|
where
|
||||||
|
runEval' :: Env -> State -> EvalM a -> ([String], Either Error a)
|
||||||
|
runEval' _ _ (Pure x) = ([], pure x)
|
||||||
|
runEval' r s (Free (ReadOp k)) = runEval' r s $ k r
|
||||||
|
runEval' r s (Free (StateGetOp k)) = runEval' r s $ k s
|
||||||
|
runEval' r _ (Free (StatePutOp s' m)) = runEval' r s' m
|
||||||
|
runEval' r s (Free (PrintOp p m)) =
|
||||||
|
let (ps, res) = runEval' r s m
|
||||||
|
in (p : ps, res)
|
||||||
|
runEval' _ _ (Free (ErrorOp e)) = ([], Left e)
|
95
a4/src/APL/Interp_Tests.hs
Normal file
95
a4/src/APL/Interp_Tests.hs
Normal file
@ -0,0 +1,95 @@
|
|||||||
|
module APL.Interp_Tests (tests) where
|
||||||
|
|
||||||
|
import APL.AST (Exp (..))
|
||||||
|
import APL.Eval (eval)
|
||||||
|
import APL.InterpIO (runEvalIO)
|
||||||
|
import APL.InterpPure (runEval)
|
||||||
|
import APL.Monad
|
||||||
|
import APL.Util (captureIO)
|
||||||
|
import Test.Tasty (TestTree, testGroup)
|
||||||
|
import Test.Tasty.HUnit (testCase, (@?=))
|
||||||
|
|
||||||
|
eval' :: Exp -> ([String], Either Error Val)
|
||||||
|
eval' = runEval . eval
|
||||||
|
|
||||||
|
evalIO' :: Exp -> IO (Either Error Val)
|
||||||
|
evalIO' = runEvalIO . eval
|
||||||
|
|
||||||
|
tests :: TestTree
|
||||||
|
tests = testGroup "Free monad interpreters" [pureTests, ioTests]
|
||||||
|
|
||||||
|
pureTests :: TestTree
|
||||||
|
pureTests =
|
||||||
|
testGroup
|
||||||
|
"Pure interpreter"
|
||||||
|
[ testCase "localEnv" $
|
||||||
|
runEval
|
||||||
|
( localEnv (const [("x", ValInt 1)]) $
|
||||||
|
askEnv
|
||||||
|
)
|
||||||
|
@?= ([], Right [("x", ValInt 1)]),
|
||||||
|
--
|
||||||
|
testCase "Let" $
|
||||||
|
eval' (Let "x" (Add (CstInt 2) (CstInt 3)) (Var "x"))
|
||||||
|
@?= ([], Right (ValInt 5)),
|
||||||
|
--
|
||||||
|
testCase "Let (shadowing)" $
|
||||||
|
eval'
|
||||||
|
( Let
|
||||||
|
"x"
|
||||||
|
(Add (CstInt 2) (CstInt 3))
|
||||||
|
(Let "x" (CstBool True) (Var "x"))
|
||||||
|
)
|
||||||
|
@?= ([], Right (ValBool True)),
|
||||||
|
--
|
||||||
|
testCase "State" $
|
||||||
|
runEval
|
||||||
|
( do
|
||||||
|
putState [(ValInt 0, ValInt 1)]
|
||||||
|
modifyState $ map (\(key, _) -> (key, ValInt 5))
|
||||||
|
getState
|
||||||
|
)
|
||||||
|
@?= ([], Right [(ValInt 0, ValInt 5)]),
|
||||||
|
--
|
||||||
|
testCase "Print" $
|
||||||
|
runEval (evalPrint "test")
|
||||||
|
@?= (["test"], Right ()),
|
||||||
|
--
|
||||||
|
testCase "Error" $
|
||||||
|
runEval
|
||||||
|
( do
|
||||||
|
_ <- failure "Oh no!"
|
||||||
|
evalPrint "test"
|
||||||
|
)
|
||||||
|
@?= ([], Left "Oh no!"),
|
||||||
|
--
|
||||||
|
testCase "Div0" $
|
||||||
|
eval' (Div (CstInt 7) (CstInt 0))
|
||||||
|
@?= ([], Left "Division by zero")
|
||||||
|
]
|
||||||
|
|
||||||
|
ioTests :: TestTree
|
||||||
|
ioTests =
|
||||||
|
testGroup
|
||||||
|
"IO interpreter"
|
||||||
|
[ testCase "print" $ do
|
||||||
|
let s1 = "Lalalalala"
|
||||||
|
s2 = "Weeeeeeeee"
|
||||||
|
(out, res) <-
|
||||||
|
captureIO [] $
|
||||||
|
runEvalIO $ do
|
||||||
|
evalPrint s1
|
||||||
|
evalPrint s2
|
||||||
|
(out, res) @?= ([s1, s2], Right ())
|
||||||
|
-- NOTE: This test will give a runtime error unless you replace the
|
||||||
|
-- version of `eval` in `APL.Eval` with a complete version that supports
|
||||||
|
-- `Print`-expressions. Uncomment at your own risk.
|
||||||
|
-- testCase "print 2" $ do
|
||||||
|
-- (out, res) <-
|
||||||
|
-- captureIO [] $
|
||||||
|
-- evalIO' $
|
||||||
|
-- Print "This is also 1" $
|
||||||
|
-- Print "This is 1" $
|
||||||
|
-- CstInt 1
|
||||||
|
-- (out, res) @?= (["This is 1: 1", "This is also 1: 1"], Right $ ValInt 1)
|
||||||
|
]
|
129
a4/src/APL/Monad.hs
Normal file
129
a4/src/APL/Monad.hs
Normal file
@ -0,0 +1,129 @@
|
|||||||
|
module APL.Monad
|
||||||
|
( envEmpty,
|
||||||
|
envExtend,
|
||||||
|
envLookup,
|
||||||
|
stateInitial,
|
||||||
|
askEnv,
|
||||||
|
modifyEffects,
|
||||||
|
localEnv,
|
||||||
|
getState,
|
||||||
|
putState,
|
||||||
|
modifyState,
|
||||||
|
evalPrint,
|
||||||
|
catch,
|
||||||
|
failure,
|
||||||
|
evalKvGet,
|
||||||
|
evalKvPut,
|
||||||
|
transaction,
|
||||||
|
EvalM,
|
||||||
|
Val (..),
|
||||||
|
EvalOp (..),
|
||||||
|
Free (..),
|
||||||
|
Error,
|
||||||
|
Env,
|
||||||
|
State,
|
||||||
|
)
|
||||||
|
where
|
||||||
|
|
||||||
|
import APL.AST (Exp (..), VName)
|
||||||
|
import Control.Monad (ap)
|
||||||
|
|
||||||
|
data Val
|
||||||
|
= ValInt Integer
|
||||||
|
| ValBool Bool
|
||||||
|
| ValFun Env VName Exp
|
||||||
|
deriving (Eq, Show)
|
||||||
|
|
||||||
|
type Error = String
|
||||||
|
|
||||||
|
type Env = [(VName, Val)]
|
||||||
|
|
||||||
|
envEmpty :: Env
|
||||||
|
envEmpty = []
|
||||||
|
|
||||||
|
envExtend :: VName -> Val -> Env -> Env
|
||||||
|
envExtend v val env = (v, val) : env
|
||||||
|
|
||||||
|
envLookup :: VName -> Env -> Maybe Val
|
||||||
|
envLookup v env = lookup v env
|
||||||
|
|
||||||
|
type State = [(Val, Val)]
|
||||||
|
|
||||||
|
stateInitial :: State
|
||||||
|
stateInitial = []
|
||||||
|
|
||||||
|
data Free e a
|
||||||
|
= Pure a
|
||||||
|
| Free (e (Free e a))
|
||||||
|
|
||||||
|
instance (Functor e) => Functor (Free e) where
|
||||||
|
fmap f (Pure x) = Pure $ f x
|
||||||
|
fmap f (Free g) = Free $ fmap (fmap f) g
|
||||||
|
|
||||||
|
instance (Functor e) => Applicative (Free e) where
|
||||||
|
pure = Pure
|
||||||
|
(<*>) = ap
|
||||||
|
|
||||||
|
instance (Functor e) => Monad (Free e) where
|
||||||
|
Pure x >>= f = f x
|
||||||
|
Free g >>= f = Free $ h <$> g
|
||||||
|
where
|
||||||
|
h x = x >>= f
|
||||||
|
|
||||||
|
data EvalOp a
|
||||||
|
= ReadOp (Env -> a)
|
||||||
|
| StateGetOp (State -> a)
|
||||||
|
| StatePutOp State a
|
||||||
|
| PrintOp String a
|
||||||
|
| ErrorOp Error
|
||||||
|
|
||||||
|
instance Functor EvalOp where
|
||||||
|
fmap f (ReadOp k) = ReadOp $ f . k
|
||||||
|
fmap f (StateGetOp k) = StateGetOp $ f . k
|
||||||
|
fmap f (StatePutOp s m) = StatePutOp s $ f m
|
||||||
|
fmap f (PrintOp p m) = PrintOp p $ f m
|
||||||
|
fmap _ (ErrorOp e) = ErrorOp e
|
||||||
|
|
||||||
|
type EvalM a = Free EvalOp a
|
||||||
|
|
||||||
|
askEnv :: EvalM Env
|
||||||
|
askEnv = Free $ ReadOp $ \env -> pure env
|
||||||
|
|
||||||
|
modifyEffects :: (Functor e, Functor h) => (e (Free e a) -> h (Free e a)) -> Free e a -> Free h a
|
||||||
|
modifyEffects _ (Pure x) = Pure x
|
||||||
|
modifyEffects g (Free e) = Free $ modifyEffects g <$> g e
|
||||||
|
|
||||||
|
localEnv :: (Env -> Env) -> EvalM a -> EvalM a
|
||||||
|
localEnv f = modifyEffects g
|
||||||
|
where
|
||||||
|
g (ReadOp k) = ReadOp $ k . f
|
||||||
|
g op = op
|
||||||
|
|
||||||
|
getState :: EvalM State
|
||||||
|
getState = Free $ StateGetOp $ \s -> pure s
|
||||||
|
|
||||||
|
putState :: State -> EvalM ()
|
||||||
|
putState s = Free $ StatePutOp s $ pure ()
|
||||||
|
|
||||||
|
modifyState :: (State -> State) -> EvalM ()
|
||||||
|
modifyState f = do
|
||||||
|
s <- getState
|
||||||
|
putState $ f s
|
||||||
|
|
||||||
|
evalPrint :: String -> EvalM ()
|
||||||
|
evalPrint p = Free $ PrintOp p $ pure ()
|
||||||
|
|
||||||
|
failure :: String -> EvalM a
|
||||||
|
failure = Free . ErrorOp
|
||||||
|
|
||||||
|
catch :: EvalM a -> EvalM a -> EvalM a
|
||||||
|
catch = error "TODO"
|
||||||
|
|
||||||
|
evalKvGet :: Val -> EvalM Val
|
||||||
|
evalKvGet = error "TODO"
|
||||||
|
|
||||||
|
evalKvPut :: Val -> Val -> EvalM ()
|
||||||
|
evalKvPut = error "TODO"
|
||||||
|
|
||||||
|
transaction :: EvalM () -> EvalM ()
|
||||||
|
transaction = error "TODO"
|
99
a4/src/APL/Util.hs
Normal file
99
a4/src/APL/Util.hs
Normal file
@ -0,0 +1,99 @@
|
|||||||
|
{-# LANGUAGE FlexibleInstances #-}
|
||||||
|
|
||||||
|
module APL.Util
|
||||||
|
( Serialize (..),
|
||||||
|
newTempDB,
|
||||||
|
captureIO,
|
||||||
|
)
|
||||||
|
where
|
||||||
|
|
||||||
|
import APL.Monad
|
||||||
|
import Control.Concurrent (threadDelay)
|
||||||
|
import Control.Exception (bracket)
|
||||||
|
import Control.Monad (guard)
|
||||||
|
import Data.Char (isDigit)
|
||||||
|
import Data.List (stripPrefix)
|
||||||
|
import Data.Maybe (mapMaybe)
|
||||||
|
import GHC.IO.Handle (hDuplicate, hDuplicateTo)
|
||||||
|
import System.Directory (listDirectory)
|
||||||
|
import System.IO
|
||||||
|
import System.Process (createPipe)
|
||||||
|
import Text.Read (readMaybe)
|
||||||
|
|
||||||
|
class Serialize a where
|
||||||
|
serialize :: a -> String
|
||||||
|
unserialize :: String -> Maybe a
|
||||||
|
|
||||||
|
instance Serialize Val where
|
||||||
|
serialize (ValInt x) = "ValInt " <> show x
|
||||||
|
serialize (ValBool b) = "ValBool " <> show b
|
||||||
|
serialize ValFun {} = error "ValFun serialization is not supported."
|
||||||
|
|
||||||
|
unserialize s =
|
||||||
|
case words s of
|
||||||
|
["ValInt", rest]
|
||||||
|
| all isDigit rest -> ValInt <$> readMaybe rest
|
||||||
|
["ValBool", rest] -> ValBool <$> readMaybe rest
|
||||||
|
_ -> Nothing
|
||||||
|
|
||||||
|
instance (Serialize a, Serialize b) => Serialize (a, b) where
|
||||||
|
serialize (a, b) =
|
||||||
|
serialize a ++ "," ++ serialize b
|
||||||
|
unserialize s =
|
||||||
|
case span (/= ',') s of
|
||||||
|
(s_a, _ : s_b) -> (,) <$> unserialize s_a <*> unserialize s_b
|
||||||
|
_ -> Nothing
|
||||||
|
|
||||||
|
instance Serialize [(Val, Val)] where
|
||||||
|
serialize kv =
|
||||||
|
unlines $ map serialize kv
|
||||||
|
unserialize =
|
||||||
|
mapM unserialize . lines
|
||||||
|
|
||||||
|
newTempDB :: IO FilePath
|
||||||
|
newTempDB = do
|
||||||
|
files <- listDirectory "."
|
||||||
|
let n = maximum (0 : mapMaybe match files) + 1
|
||||||
|
tempFile = "temp" ++ show n ++ ".txt"
|
||||||
|
writeFile tempFile ""
|
||||||
|
pure $ tempFile
|
||||||
|
where
|
||||||
|
match :: FilePath -> Maybe Int
|
||||||
|
match s = do
|
||||||
|
s' <- stripPrefix "temp" s
|
||||||
|
let (n_s, rest) = (takeWhile isDigit s', dropWhile isDigit s')
|
||||||
|
guard $ rest == ".txt"
|
||||||
|
readMaybe n_s
|
||||||
|
|
||||||
|
captureIO :: [String] -> IO a -> IO ([String], a)
|
||||||
|
captureIO inputs m = do
|
||||||
|
hFlush stdout
|
||||||
|
threadDelay 50000 -- Needed to make sure things are actually flushed
|
||||||
|
stdin' <- hDuplicate stdin
|
||||||
|
stdout' <- hDuplicate stdout
|
||||||
|
|
||||||
|
(inR, inW) <- createPipe
|
||||||
|
(outR, outW) <- createPipe
|
||||||
|
|
||||||
|
hSetBuffering inW NoBuffering
|
||||||
|
hSetBuffering outW NoBuffering
|
||||||
|
|
||||||
|
bracket
|
||||||
|
( do
|
||||||
|
inR `hDuplicateTo` stdin
|
||||||
|
outW `hDuplicateTo` stdout
|
||||||
|
)
|
||||||
|
( \_ -> do
|
||||||
|
stdin' `hDuplicateTo` stdin
|
||||||
|
stdout' `hDuplicateTo` stdout
|
||||||
|
mapM_ hClose [stdin', stdout', inR, inW, outW]
|
||||||
|
)
|
||||||
|
( \_ -> do
|
||||||
|
mapM_ (hPutStrLn inW) inputs
|
||||||
|
hFlush inW
|
||||||
|
|
||||||
|
res <- m
|
||||||
|
|
||||||
|
output <- hGetContents outR -- hGetContents closes outR
|
||||||
|
pure (lines output, res)
|
||||||
|
)
|
Reference in New Issue
Block a user