-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathRuntime.hs
120 lines (78 loc) · 3.77 KB
/
Runtime.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
module Runtime where
import Data.IORef
import Parser
import ErrorCatcher
import Control.Monad.Except
-- IORef (like the ST Monad) is a 'state thread'
-- these let you do stateful computations that can be executed as a unit, without the state escaping to the rest of the progr
-- defined a type for our environment
type Env = IORef [(String, IORef LispVal)]
-- this is a map that maps string to mutable LispVals. Which means the value of a variable can change.
-- plus, this map itself can change (ie mutable). Which means new varibles can be added
{-
Note,
newIORef is a function, that accepts an argument
and wraps it in IORef (i.e makes it mutable),
and then wraps the whole thing in IO.
Why IO?
Because IORefs can only be used within the IO monad.
> :t newIORef
newIORef :: a -> IO (IORef a)
-}
nullEnv :: IO Env
nullEnv = newIORef []
-- we use a monad transformer ExceptT,
-- monad transformers let us combine the functionality of two monads.
-- ExceptT lets us layer error-handling functionality on top of the IO monad
-- So create a type synonym for our combined monad:
type IOThrowsError = ExceptT LispError IO
-- ExceptT takes one more argument, the return type of the function.
-- helper functions
liftThrows :: ThrowsError a -> IOThrowsError a
liftThrows (Left err) = throwError err
liftThrows (Right val) = return val
runIOThrows :: IOThrowsError String -> IO String
runIOThrows action = runExceptT (trapError action) >>= return . extractValue
--
--------------------------------------------------
-- methods for handling the runtime environment --
--------------------------------------------------
-- determine if a given variable is already bound in the environment
isBound :: Env -> String -> IO Bool
isBound envRef var = readIORef envRef >>= return . maybe False (const True) . lookup var
-- ```readIORef envRef``` returns a map in the IO context. and pipes it to the 2nd statment,
-- which tells us if the variable is present in the environment/map.
-- a function to retrieve the current value of a variable:
getVar :: Env -> String -> IOThrowsError LispVal
getVar envRef var = do env <- liftIO $ readIORef envRef
maybe (throwError $ UnboundVar "Getting an unbound variable" var)
(liftIO . readIORef)
(lookup var env)
-- readIORef takes the IORef into the IO context.
-- liftIO takes values from IO Context, and into the IOThrowsError context
-- a function to set values:
setVar :: Env -> String -> LispVal -> IOThrowsError LispVal
setVar envRef var value = do env <- liftIO $ readIORef envRef
maybe (throwError $ UnboundVar "Setting an unbound variable" var)
(liftIO . (flip writeIORef value))
(lookup var env)
return value
-- implementing define,
-- which sets a variable if already bound or creates a new one if not
defineVar :: Env -> String -> LispVal -> IOThrowsError LispVal
defineVar envRef var value = do
alreadyDefined <- liftIO $ isBound envRef var
if alreadyDefined
then setVar envRef var value >> return value
else liftIO $ do
valueRef <- newIORef value
env <- readIORef envRef
writeIORef envRef ((var, valueRef) : env)
return value
-- todo: add explanation
bindVars :: Env -> [(String, LispVal)] -> IO Env
bindVars envRef bindings = readIORef envRef >>= extendEnv bindings >>= newIORef
where extendEnv bindings env = liftM (++ env) (mapM addBinding bindings)
addBinding (var, value) = do ref <- newIORef value
return (var, ref)
-- to do: notes on IORef, notes on liftIO