-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy pathMiniRubyInterpreter.hs
More file actions
413 lines (336 loc) · 17.8 KB
/
MiniRubyInterpreter.hs
File metadata and controls
413 lines (336 loc) · 17.8 KB
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
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
module MiniRubyInterpreter
( runProg
, RuntimeError (..)
)
where
import MiniRubyAST
import Control.Applicative
import Control.Monad
import Data.List
import qualified Data.Map as Map
data RuntimeError = RuntimeError {
unRuntimeError :: String
} deriving (Show, Eq)
-- | Give the printed representation of a value.
printed :: Value -> String
printed (IntValue x) = show x
printed (StringValue s) = s
printed (SymbolValue s) = s
printed (BooleanValue b) = show b ++ "!"
printed (ReferenceValue ref) = "#<object " ++ show ref ++ ">"
type Store = Map.Map
type ObjectFields = Store Name Value
type ObjectStore = Store ObjectReference ObjectState
type MethodVariables = Store Name Value
-- | The global state of the program execution.
data GlobalState = GlobalState {
prog :: Prog,
output :: String,
store :: ObjectStore,
storeIndex :: Int
} deriving (Show)
-- | The state of a single object.
data ObjectState = ObjectState {
klassName :: Name,
fields :: ObjectFields
} deriving (Show)
-- | The state of a method execution.
data MethodState = MethodState {
globalState :: GlobalState,
current :: ObjectReference,
vars :: MethodVariables
} deriving (Show)
-- | The basic monad in which execution of a MiniRuby program takes place.
-- Maintains the global state, the running output, and whether or not
-- an error has occurred.
data MiniRubyM a = MiniRubyM {
runMiniRubyM :: GlobalState -> Either RuntimeError (a,GlobalState)
}
instance Functor MiniRubyM where
fmap = liftM
instance Applicative MiniRubyM where
pure = return
(<*>) = ap
instance Monad MiniRubyM where
return x = MiniRubyM (\s -> Right (x,s))
(MiniRubyM h) >>= f = MiniRubyM $ \s -> case h s of
Left x -> Left x
Right (a,s') -> runMiniRubyM (f a) s'
fail str = MiniRubyM (\_ -> Left $ RuntimeError str)
getGlobalState :: MiniRubyM GlobalState
getGlobalState = MiniRubyM (\s -> Right (s,s))
putGlobalState :: GlobalState -> MiniRubyM ()
putGlobalState s = MiniRubyM (\_ -> Right ((),s))
getObjectStore :: MiniRubyM ObjectStore
getObjectStore = do s <- getGlobalState
return $ store s
putObjectStore :: ObjectStore -> MiniRubyM ()
putObjectStore gs = do s <- getGlobalState
putGlobalState (s {store = gs})
-- Cannot get objects we have not created. (Not really an issus for the end users, since we cannot
-- remove objects either..)
lookupObject :: ObjectReference -> MiniRubyM ObjectState
lookupObject objRef = do st <- getObjectStore
case Map.lookup objRef st of
Nothing -> fail "Object not found."
Just x -> return x
setObject :: ObjectReference -> ObjectState -> MiniRubyM ()
setObject objRef os = do oldStore <- getObjectStore
putObjectStore $ Map.insert objRef os oldStore
getObjectState :: MiniRubyMethodM ObjectState
getObjectState = MiniRubyMethodM $ \s -> do os <- lookupObject (current s)
return (os,s)
-- putObjectState :: ObjectState -> MiniRubyMethodM ()
-- putObjectState s' = MiniRubyMethodM $ \s -> liftMiniRubyM $ setObject (current s) s'
putObjectState :: ObjectState -> MiniRubyMethodM ()
putObjectState s' = MiniRubyMethodM $ \ms -> let gs = (globalState ms)
oldStore = (store gs)
store' = Map.insert (current ms) s' oldStore
gs' = gs {store = store'}
ms' = ms {globalState = gs'}
in return ((),ms')
allocUniqID :: MiniRubyM ObjectReference
allocUniqID = do s <- getGlobalState
putGlobalState (s {storeIndex = storeIndex s + 1})
return $ storeIndex s
-- | The monad in which methods (and constructors and receive actions)
-- execute. Runs on top of 'MiniRubyM' - maintains the reference to self,
-- as well as the method variables.
data MiniRubyMethodM a = MiniRubyMethodM {
runMiniRubyMethodM :: MethodState -> MiniRubyM (a,MethodState)
}
instance Functor MiniRubyMethodM where
fmap = liftM
instance Applicative MiniRubyMethodM where
pure = return
(<*>) = ap
-- | The monad in which methods (and constructors and receive actions)
-- execute. Runs on top of 'MiniRubyM' - maintains the reference to self,
-- as well as the method variables.
--
-- Note that since MiniRubyMethodM runs on top of MiniRubyM, a MiniRubyMethodM
-- action has access to the global state (through liftMiniRubyM).
instance Monad MiniRubyMethodM where
return x = (liftMiniRubyM . return) x --MiniRubyMethodM (\s -> MiniRubyM (\z -> Right ((x,s),z)))
fail x = (liftMiniRubyM . fail) x -- MiniRubyMethodM (\_ -> MiniRubyM (\_ -> Left $ RuntimeError x))
(MiniRubyMethodM h) >>= f = MiniRubyMethodM $ \ms -> case runMiniRubyM (h ms) (globalState ms) of
Left str -> fail (unRuntimeError str)
Right ((x,s'),_) -> runMiniRubyMethodM (f x) s'
-- | Perform a 'MiniRubyM' operation inside a 'MiniRubyMethodM'.
liftMiniRubyM :: MiniRubyM a -> MiniRubyMethodM a
liftMiniRubyM monad = MiniRubyMethodM (\ms -> case (runMiniRubyM monad) (globalState ms) of
Left x -> fail (unRuntimeError x)
Right (v,gs) -> MiniRubyM (\gs' -> Right ((v,ms {globalState = gs} ),gs')))
-- | Who are we?
askSelf :: MiniRubyMethodM ObjectReference
askSelf = do s <- getMethodState
return (current s)
-- bind variables
bindVars :: [Name] -> [Value] -> MiniRubyMethodM Value
bindVars [n] [v] = bindVar (n,v)
bindVars (n:ns) (v:vs) = do bindVar (n,v)
bindVars ns vs
bindVars _ _ = fail "Wrong number of arguments in `case`"
bindVar :: (Name,Value) -> MiniRubyMethodM Value
bindVar (name,value) = do s <- getMethodState
putMethodState (s {vars = Map.insert name value (vars s)})
return value
getVar :: Name -> MiniRubyMethodM Value
getVar name = do ms <- getMethodState
case Map.lookup name (vars ms) of
Nothing -> fail $ "Variable or parameter `" ++ name ++ "` could not be found"
Just x -> return x
getMethodState :: MiniRubyMethodM MethodState
--getMethodState = undefined
--getMethodState = MiniRubyMethodM (\s -> Right (s,s))
getMethodState = MiniRubyMethodM (\s -> MiniRubyM (\z -> Right ((s,s),z)))
putMethodState :: MethodState -> MiniRubyMethodM ()
putMethodState s = MiniRubyMethodM (\_ -> MiniRubyM (\z -> Right (((),s),z)))
--putMethodState s = MiniRubyMethodM (\_ -> Right((),s))
-- | Find the declaration of the class with the given name, or cause
-- an error if that name is not a class.
findClassDecl :: Name -> MiniRubyM ClassDecl
findClassDecl name = do s <- getGlobalState
case findClassDeclHelper name (prog s) of
Nothing -> fail $ "Could not find a `" ++ name ++ "` class."
Just x -> return x
findClassDeclHelper :: Name -> [ClassDecl] -> Maybe ClassDecl
findClassDeclHelper _ [] = Nothing
findClassDeclHelper name (e:es) = if (className e) == name
then Just e
else findClassDeclHelper name es
-- | Instantiate the class with the given name, passing the given
-- values to the constructor.
createObject :: Name -> MiniRubyM ObjectReference
createObject name = do i <- allocUniqID
setObject i (ObjectState {klassName = name, fields = Map.empty})
return i
getConstructor :: ClassDecl -> ConstructorDecl
getConstructor cld = case (classConstructor cld) of
Nothing -> emptyContructorDecl
Just x -> x
emptyContructorDecl :: ConstructorDecl
emptyContructorDecl = MethodDecl { methodParameters = [],
methodBody = []
}
getMethod :: ClassDecl -> Name -> Maybe MethodDecl
getMethod cld methodName = getMethodHelper methodName (classMethods cld)
getMethodHelper :: Name -> [NamedMethodDecl] -> Maybe MethodDecl
getMethodHelper _ [] = Nothing
getMethodHelper name ((NamedMethodDecl name2 methodDecl):es) = if name == name2
then Just methodDecl
else getMethodHelper name es
----------------- Evaluate expressions ------------------
evalExprs :: [Expr] -> MiniRubyMethodM Value
evalExprs [] = return $ IntValue 0
evalExprs [e] = evalExpr e
evalExprs ((Return e):_) = evalExpr e
evalExprs (e:es) = evalExpr e >> evalExprs es
evalExprsEach :: [Expr] -> MiniRubyMethodM [Value]
evalExprsEach [] = return []
evalExprsEach [e] = do v <- evalExpr e
return [v]
evalExprsEach (e:es) = do v <- evalExpr e
vs <- evalExprsEach es
return (v:vs)
evalArit :: (Integer -> Integer -> Integer) -> Expr -> Expr -> MiniRubyMethodM Value
evalArit f e1 e2 = do v1 <- evalExpr e1
v2 <- evalExpr e2
case (v1,v2) of
(IntValue v1',IntValue v2') -> return $ IntValue $ f v1' v2'
_ -> fail "Cannot do arithmetic with non-integers"
evalBool :: (Integer -> Integer -> Bool) -> Expr -> Expr -> MiniRubyMethodM Value
evalBool f e1 e2 = do v1 <- evalExpr e1
v2 <- evalExpr e2
case (v1,v2) of
(IntValue v1',IntValue v2') -> return $ BooleanValue $ f v1' v2'
_ -> fail "Cannot do comparison with non-integers"
evalExpr :: Expr -> MiniRubyMethodM Value
evalExpr (Minus e1 e2) = evalArit (-) e1 e2
evalExpr (Plus e1 e2) = evalArit (+) e1 e2
evalExpr (Times e1 e2) = evalArit (*) e1 e2
evalExpr (DividedBy e1 e2) = evalArit div e1 e2
evalExpr (LessThan e1 e2) = evalBool (<) e1 e2
evalExpr (GreaterThan e1 e2) = evalBool (>) e1 e2
evalExpr (IntConst i) = return $ IntValue i
evalExpr (StringConst str) = return $ StringValue str
evalExpr (BooleanConst b) = return $ BooleanValue b
evalExpr (SetVar varName e1) = do varValue <- evalExpr e1
bindVar (varName,varValue)
evalExpr (SetField fieldName e1) = do fieldValue <- evalExpr e1
os <- getObjectState
putObjectState (os {fields = Map.insert
fieldName fieldValue (fields os) })
return fieldValue
evalExpr (ReadField fieldName) = do os <- getObjectState
case Map.lookup fieldName (fields os) of
Nothing -> fail $ "Field `" ++ fieldName ++ "` could not be found"
Just x -> return x
evalExpr (ReadVar varName) = getVar varName
evalExpr (New className' exprParams) = do
args <- evalExprsEach exprParams
i <- liftMiniRubyM $ createObject className'
cd <- liftMiniRubyM $ findClassDecl className'
params <- assignVars (methodParameters $ getConstructor cd) args
-- _ is the result of the constructor method. It is discarded.
_ <- liftMiniRubyM $ evalMethodBody i params (methodBody $ getConstructor cd)
return $ ReferenceValue i
evalExpr (CallMethod receiverExpr "send" (exprParam:exprParams)) = do (StringValue methodName) <- evalExpr exprParam
args <- evalExprsEach exprParams
objectRef <- evalExpr receiverExpr
methodCall objectRef methodName args
evalExpr (CallMethod receiverExpr methodName exprParams) = do args <- evalExprsEach exprParams
objectRef <- evalExpr receiverExpr
methodCall objectRef methodName args
evalExpr (Self) = do v <- askSelf
return $ ReferenceValue v
evalExpr (Match expr cases) = do v <- evalExpr expr
patternMatch v cases
evalExpr (Return expr ) = evalExpr expr
evalExpr x = fail $ "Using unpermitted expression: " ++ show x
patternMatch :: Value -> Cases -> MiniRubyMethodM Value
patternMatch v ((p,exprs):cs) = case (v,p) of
(IntValue i,ConstInt j) -> if i == j
then evalExprs exprs
else patternMatch v cs
(StringValue i,ConstString j) -> if i == j
then evalExprs exprs
else patternMatch v cs
(v',AnyValue k) -> do bindVar (k,v')
evalExprs exprs
(_,_) -> patternMatch v cs
methodCall :: Value -> Name -> [Value] -> MiniRubyMethodM Value
methodCall receiver "puts" [v] = puts v
methodCall (ReferenceValue objRef) methodName args = do
className' <- lookupClassName objRef
cd <- liftMiniRubyM $ findClassDecl className'
case getMethod cd methodName of
Nothing ->
case classReceive cd of
Nothing -> fail "No receive method defined"
Just x -> do params <- assignVars (receiveParameters x) (StringValue methodName:args)
-- After a methodcall, we discard the methodstate.
(methodReturn,_) <- liftMiniRubyM $ evalMethodBody objRef params (receiveBody x)
return methodReturn
Just x ->
do params <- assignVars (methodParameters x) args
-- After a methodcall, we discard the methodstate.
(methodReturn,_) <- liftMiniRubyM $ evalMethodBody objRef params (methodBody x)
return methodReturn
methodCall _ _ _ = fail "Cannot call methods on non-objects. (only puts)"
assignVars :: [Name] -> [Value] -> MiniRubyMethodM MethodVariables
assignVars names values = case assignVarsHelper names values Map.empty of
Nothing -> fail "Wrong number of arguments!"
Just x -> return x
assignVarsHelper :: [Name] -> [Value] -> MethodVariables -> Maybe MethodVariables
assignVarsHelper [] [] assigned = Just assigned
assignVarsHelper _ [] _ = Nothing
assignVarsHelper [] _ _ = Nothing
assignVarsHelper (n:ns) (v:vs) assigned = assignVarsHelper ns vs (Map.insert n v assigned)
-- | Evaluate a method body - the passed arguments are the object in
-- which to run, the initial variable bindings (probably the
-- parameters of the method, constructor or receive action), and the
-- body. Returns a value and the new state of the object.
evalMethodBody :: ObjectReference -> MethodVariables -> Exprs -> MiniRubyM (Value, MethodState)
evalMethodBody obj variables e = do
st <- getGlobalState
os <- lookupObject obj
(v,ms) <- runMiniRubyMethodM (evalExprs e) $ MethodState { current = obj,
globalState = st,
vars = variables }
putGlobalState (globalState ms)
return (v,ms)
-- function that goes from objRef to className
lookupClassName :: ObjectReference -> MiniRubyMethodM Name
lookupClassName objRef = do objState <- liftMiniRubyM $ lookupObject objRef
return (klassName objState)
puts :: Value -> MiniRubyMethodM Value
puts v = do s <- liftMiniRubyM getGlobalState
liftMiniRubyM $ putGlobalState (s {output = (output s) ++ printed v ++ "\n"})
return v
-- runProg :: Prog -> Either RuntimeError String
runProg :: Prog -> Either RuntimeError String
runProg p = case starter p of
Right ((_,_), gs) -> Right (output gs)
Left x -> Left x
scanForDoubleClassNames :: Prog -> MiniRubyM ()
scanForDoubleClassNames p = if allClassNames p /= (nub (allClassNames p))
then fail "There are dublicate class definitions"
else return ()
allClassNames :: Prog -> [Name]
allClassNames p = fmap className p
starter :: Prog -> Either RuntimeError ((Value, MethodState), GlobalState)
starter p = let monad = do
scanForDoubleClassNames p
cd <- findClassDecl "Main"
objRef <- createObject "Main"
result <- evalMethodBody objRef Map.empty (methodBody $ getConstructor cd)
return result
in runMiniRubyM monad $ initGlobalState p
initGlobalState :: Prog -> GlobalState
initGlobalState p = GlobalState {
prog = p,
output = "",
store = Map.empty,
storeIndex = 0
}