2 {-# Language MultiParamTypeClasses, TypeSynonymInstances, FlexibleInstances #-}
3 {-# Language GeneralizedNewtypeDeriving #-}
12 import Prelude hiding (lookup)
15 import qualified Data.Map as Map
17 import Control.Exception
18 import Control.Monad.Identity
19 import Control.Monad.Except
20 import Control.Monad.Reader
21 import Control.Monad.State
22 import Control.Monad.Writer
23 import Control.Applicative hiding (Const)
25 import System.IO.Error
26 import qualified Text.ParserCombinators.ReadP as Read
27 import Text.ParserCombinators.ReadP ((<++))
28 import qualified Text.Read as Read (lift, readPrec)
30 {-------------------------------------------------------------------}
31 {- The pure expression language -}
32 {-------------------------------------------------------------------}
34 data Val = I Int | B Bool
37 instance Show Val where
42 | Add Expr Expr | Sub Expr Expr | Mul Expr Expr | Div Expr Expr
43 | And Expr Expr | Or Expr Expr | Not Expr
44 | Eq Expr Expr | Gt Expr Expr | Lt Expr Expr
48 instance Show Expr where
49 show (Const v) = show v
50 show (Add e1 e2) = show e1 ++ " + " ++ show e2
51 show (Sub e1 e2) = show e1 ++ " - " ++ show e2
52 show (Mul e1 e2) = show e1 ++ " * " ++ show e2
53 show (Div e1 e2) = show e1 ++ " / " ++ show e2
54 show (And e1 e2) = show e1 ++ " & " ++ show e2
55 show (Or e1 e2) = show e1 ++ " | " ++ show e2
56 show (Not e) = "!" ++ show e
57 show (Eq e1 e2) = show e1 ++ " == " ++ show e2
58 show (Gt e1 e2) = show e1 ++ " > " ++ show e2
59 show (Lt e1 e2) = show e1 ++ " < " ++ show e2
62 instance Read Expr where
63 readPrec = Read.lift pExpr
64 where pExpr :: Read.ReadP Expr
65 pExpr = pVar <|> pLit <++ pBinOp
66 pBrackets = Read.between (Read.char '(') (Read.char ')')
68 pVar = Var <$> Read.munch1 isLetter
69 pLit = pLit' B <|> pLit' I
70 pLit' x = Const . x <$> Read.readS_to_P (readsPrec 10000)
81 [ Read.string "==" $> Eq
82 , Read.char '+' $> Add
83 , Read.char '-' $> Sub
88 type Env = Map.Map Name Val
90 lookup :: Name -> Env -> Eval Val
91 lookup k t = case Map.lookup k t of
93 Nothing -> throwError ("Unknown variable " ++ k)
95 {-- Monadic style expression evaluator,
96 -- with error handling and Reader monad instance to carry dictionary
99 type Eval a = ReaderT Env (ExceptT String Identity) a
100 runEval env ex = runIdentity ( runExceptT ( runReaderT ex env) )
102 evali :: (Int -> Int -> Int) -> Expr -> Expr -> Eval Val
103 evali op e0 e1 = do e0' <- eval e0
106 (I i0, I i1) -> return $ I (i0 `op` i1)
107 _ -> throwError "type error in arithmetic expression"
109 evalb :: (Bool -> Bool -> Bool) -> Expr -> Expr -> Eval Val
110 evalb op e0 e1 = do e0' <- eval e0
113 (B i0, B i1) -> return $ B (i0 `op` i1)
114 _ -> throwError "type error in boolean expression"
116 evalib :: (Int -> Int -> Bool) -> Expr -> Expr -> Eval Val
117 evalib op e0 e1 = do e0' <- eval e0
120 (I i0, I i1) -> return $ B (i0 `op` i1)
121 _ -> throwError "type error in arithmetic expression"
123 eval :: Expr -> Eval Val
124 eval (Const v) = return v
125 eval (Add e0 e1) = evali (+) e0 e1
126 eval (Sub e0 e1) = evali (-) e0 e1
127 eval (Mul e0 e1) = evali (*) e0 e1
128 eval (Div e0 e1) = evali div e0 e1
130 eval (And e0 e1) = evalb (&&) e0 e1
131 eval (Or e0 e1) = evalb (||) e0 e1
133 eval (Not e0 ) = evalb (const not) e0 (Const (B True))
134 where not2 a _ = not a -- hack, hack
136 eval (Eq e0 e1) = evalib (==) e0 e1
137 eval (Gt e0 e1) = evalib (>) e0 e1
138 eval (Lt e0 e1) = evalib (<) e0 e1
140 eval (Var s) = do env <- ask
144 {-------------------------------------------------------------------}
145 {- The statement language -}
148 data Statement = Assign String Expr
149 | If Expr Statement Statement
150 | While Expr Statement
152 | Seq Statement Statement
153 | Try Statement Statement
157 -- | A record of stuff being printed and run
158 data Effect = Effect Statement Env [String]
161 newtype Interpreter a = Interpreter { runI :: ExceptT String (WriterT [Effect] (State Env)) a }
162 deriving (Functor, Applicative, Monad, MonadState Env, MonadWriter [Effect], MonadError String)
164 logStatement :: Statement -> Env -> Interpreter ()
165 logStatement s e = output >>= (Interpreter . lift . tell . pure . Effect s e)
166 where output :: Interpreter [String]
168 | (Print e) <- s = pure . show <$> evalI e
169 | otherwise = return []
171 hoistEither :: Either String b -> Interpreter b
172 hoistEither = Interpreter . ExceptT . return
174 evalI :: Expr -> Interpreter Val
175 evalI e = get >>= hoistEither . flip runEval (eval e)
177 testProg = Seq (Assign "x" (Const (I 0))) loop
178 where loop = Seq (Print (Var "x"))
179 (Seq (Assign "x" (Add (Var "x") (Const (I 1))))
181 testTry = Try (Print (Add (Const (I 3)) (Const (B True))))
182 (Print (Const (I 0)))
184 exec :: Statement -> Interpreter ()
185 exec s = get >>= logStatement s >> go s
189 new <- hoistEither $ runEval env (eval e)
190 modify $ Map.insert n new
191 go (Seq s1 s2) = exec s1 >> exec s2
192 go (If cond sThen sElse) = do
197 go w@(While cond s) = do
199 when (res == B True) $ exec s >> exec w
200 go (Try s catch) = exec s `catchError` const (exec catch)
201 go (Print _) = pure () -- the printing is done in 'logStatement'
204 runInterpreter :: Interpreter a -> [Effect]
206 let (result, effects) = fst $ flip runState mempty $ runWriterT $ runExceptT $ runI f
209 type Breakpoint = Expr
212 hSetBuffering stdin LineBuffering -- read stuff one line at a time
213 go (runInterpreter (exec testProg)) [] [] `catch` (\e -> unless (isEOFError e) (throw e))
215 go :: [Effect] -> [Effect] -> [Breakpoint] -> IO ()
216 go [] _ _ = return ()
217 -- go bps (e@(EffectPrint str):xs) done = putStrLn str >> go bps xs (e:done)
218 go list@(e@(Effect s env output):xs) done bps = do
225 "n" -> mapM_ putStrLn output >> go xs (e:done) bps
228 (y:ys) -> go (y:list) ys bps
229 ('b':' ':exprStr) -> let expr = read exprStr in go list done (expr:bps)
230 "c" -> continue xs (e:done) bps
231 "?" -> printHelp >> go list done bps
233 putStrLn $ "I don't know what '" ++ c ++ "' means. Enter ? for help"
236 -- | Continues until the first breakpoint is hit
237 continue :: [Effect] -> [Effect] -> [Breakpoint] -> IO ()
238 continue [] _ _ = return ()
239 continue (e@(Effect _ env output):xs) done bps = do
240 mapM_ putStrLn output
241 case hitBreakpoint of
243 withDebug $ putStrLn $ "Hit breakpoint: " ++ show bp
245 Nothing -> continue xs (e:done) bps
247 hitBreakpoint :: Maybe Breakpoint
248 hitBreakpoint = foldl f Nothing bps
249 f :: Maybe Breakpoint -> Breakpoint -> Maybe Breakpoint
250 f Nothing cond = case runEval env (eval cond) of
251 Right (B True) -> Just cond
255 printProg :: [Effect] -> [Effect] -> IO ()
256 printProg [] _ = putStrLn "Completed"
257 printProg (current:next) done = mapM_ putStrLn ls
258 where ls = above ++ [currentS] ++ below
259 currentS = " @" ++ trunc (show (getStatement current))
260 above = map (" " ++) $ sample done
261 below = map (" " ++) $ sample next
262 sample = map (trunc . show . getStatement) . take 3
263 getStatement (Effect s _ _) = s
265 printEnv :: Env -> IO ()
266 printEnv e = putStr s
267 where s = unlines $ Map.foldlWithKey f [] e
268 f acc k v = acc ++ [k ++ ": " ++ show v]
270 printHelp = withDebug $ do
271 putStrLn "Available commands:"
272 putStrLn " n Next statement"
273 putStrLn " p Previous statement"
274 putStrLn " ? Show help"
275 putStrLn " c Continue to breakpoint"
276 putStrLn " b <expr> Set conditional breakpoint"
278 -- | Ansi escapes any output to be kind of gray
279 withDebug :: IO a -> IO a
281 putStr "\ESC[38;5;240m"
286 -- | Truncates a string *lazily*
287 trunc :: String -> String
289 | tooLong s 0 = take 64 s ++ "..."
291 where tooLong "" n = n > 64
292 tooLong (_:_) 64 = True
293 tooLong (_:xs) n = tooLong xs (n + 1)