-
Notifications
You must be signed in to change notification settings - Fork 1
/
Repl.hs
127 lines (100 loc) · 4.79 KB
/
Repl.hs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
{-|
Module : Repl
Description : The Read-Eval-Print-Loop for ULC.
Copyright : (c) Luke Geeson, 2018
License : GPL-3
Maintainer : [email protected]
Stability : stable
Portability : POSIX
The "Repl" module provides the Read-eval-print-loop for ULC.
This is the top level interface between the user and the interpreter.
-}
module Repl where
-- ULC Imports.
import ULC
import Parser
(Command(..), parseReplCommand)
-- Tool Imports.
import Data.List
import qualified Data.Map as M
import Control.Monad.Trans
import Control.Monad.Trans.State.Strict
import Control.Monad.Catch
import System.Console.Haskeline
import System.Directory
-- | Top-level repl function
replMain :: IO ()
replMain = do
putStrLn "Welcome to the Untyped λ-calculus REPL"
putStrLn "Type some terms or press Enter to leave."
evalStateT (runInputT settings repl) M.empty
-- | Stores variables from let expressions at runtime
type Environment = M.Map String ULC.Term
type Interpreter = InputT (StateT Environment IO)
-- | Settings defining how tab-completion and input history work for the Haskeline session.
settings :: Settings (StateT Environment IO)
settings = Settings contextCompletion Nothing True
where
contextCompletion :: CompletionFunc (StateT Environment IO)
contextCompletion = completeWord Nothing " ()λ\\." completions
completions :: String -> StateT Environment IO [Completion]
completions xs = map (\x -> Completion x x True) . filter (xs `isPrefixOf`) . (comms ++) . M.keys <$> get
comms :: [String]
comms = [":reductions", ":let", ":load"]
-- | Marks an InputT action as interruptible, so that pressing Ctrl+C will terminate
-- the action, returning a default value, instead of terminating the program.
interruptible :: (MonadIO m, MonadMask m) => a -> InputT m a -> InputT m a
interruptible d x = handle (\Interrupt -> outputStrLn "interrupted" >> pure d) (withInterrupt x)
-- | REPL loop, takes input reduces and prints result, or exits out.
repl :: Interpreter ()
repl = do ms <- getInputLine "> "
case ms of
Nothing -> repl
Just "" -> outputStrLn "Goodbye."
Just s -> do case parseReplCommand s of
Nothing -> outputStrLn ("could not parse " ++ show s)
Just c -> do env <- lift get
interruptible () (handleCommand (formatCommand env c))
repl
-- Dispatches a procedure for each type of command.
handleCommand :: Command -> Interpreter ()
handleCommand None = return ()
handleCommand (T t) = outputStrLn (prependTerm t (reduce t))
handleCommand (Reds t) = mapM_ outputStrLn (prependedReductions t)
handleCommand (Load fp) = handleLoad fp
handleCommand (Let v t) = do lift get >>= lift . put . M.insert v t
outputStrLn ("saved " ++ v ++ " = " ++ show t)
-- | Substitutes named terms from the environment into
-- free occurrences of those names within command terms
formatCommand :: Environment -> Command -> Command
formatCommand env c = case c of
T t -> T (formatTerm t)
Reds t -> Reds (formatTerm t)
Let s t -> Let s (formatTerm t)
c -> c
where
formatTerm :: Term -> Term
formatTerm = flip (M.foldlWithKey (\t1 v t2 -> ULC.substitute t1 (ULC.Var v, t2))) env
-- | Function prepends ~> arrows or prints existing term if no reds occur
prependedReductions :: Term -> [String]
prependedReductions t = let xs = reductions t in if null xs
then ["= " ++ show t]
else map (\x -> "~> " ++ show x) xs
-- | Function prepends reduction ops for one multi-step reduction
prependTerm :: ULC.Term -> ULC.Term -> String
prependTerm x y = if x == y then "= " ++ show x else "~>* " ++ show y
-- | Helper function to load and run a script of interpreter commands
handleLoad :: FilePath -> Interpreter ()
handleLoad fp = do exists <- liftIO (doesFileExist fp)
if exists
then do e <- parseCommands <$> liftIO (readFile fp)
case e of
Left (n,l) -> outputStrLn ("could not parse " ++ show l ++ " on line " ++ show n)
Right cs -> mapM_ (\c -> lift get >>= handleCommand . flip formatCommand c) cs
else outputStrLn ("file " ++ fp ++ " does not exist")
where
parseCommands :: String -> Either (Int,String) [Command]
parseCommands = mapM (toEither . fmap parseReplCommand) . filter (not . null . snd) . zipWith (\n l -> ((n,l),l)) [1..] . lines
toEither :: (a, Maybe b) -> Either a b
toEither (a, Nothing) = Left a
toEither (_, Just b) = Right b