-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathTypeChecker.hs
350 lines (283 loc) · 13.1 KB
/
TypeChecker.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
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
module TypeChecker where
import Data.Map as Map
import Data.Maybe (fromJust)
import Data.String
import Control.Monad.State
import Control.Monad.Reader
import Control.Monad.Except
import System.IO
import qualified AbsSoya as Gram
type Pos = Gram.BNFC'Position
data Type = Int | Str | Bool | None | Tuple [Type] | List Type | Fun Type [Type] deriving (Eq, Show)
data TEnv = TEnv { types :: Map String Type,
mutable :: Map String Bool,
in_function :: Maybe String,
func_pos :: Maybe Pos,
in_loop :: Bool } deriving (Eq, Show)
type TypeMonad a = ExceptT String (Reader TEnv) a
gtypeToType :: Gram.Type -> Type
gtypeToType (Gram.Int _) = Int
gtypeToType (Gram.Str _) = Str
gtypeToType (Gram.Bool _) = Bool
gtypeToType (Gram.List _ t) = List (gtypeToType t)
addVariableType :: String -> Type -> TEnv -> TEnv
-- by default variables are mutable
addVariableType name typ env = env { types = Map.insert name typ (types env), mutable = Map.insert name True (mutable env) }
addImmutableVariableType :: String -> Type -> TEnv -> TEnv
addImmutableVariableType name typ env = env { types = Map.insert name typ (types env), mutable = Map.insert name False (mutable env) }
inFunction :: String -> TEnv -> TEnv
inFunction name env = env { in_function = Just name }
inLoop :: Bool -> TEnv -> TEnv
inLoop b env = env { in_loop = b }
funcPos :: Pos -> TEnv -> TEnv
funcPos pos env = env { func_pos = Just pos }
-- function to print error message
tError :: String -> Pos -> TypeMonad a
tError msg pos = throwError $ "[TYPE ERROR at " ++ show pos ++ "]: " ++ msg
----------------- Expressions -----------------
checkExpr :: Gram.Expr -> TypeMonad Type
checkExpr (Gram.ELitInt _ _) = return Int
checkExpr (Gram.ELitTrue _) = return Bool
checkExpr (Gram.ELitFalse _) = return Bool
checkExpr (Gram.EString _ _) = return Str
checkExpr (Gram.ERel pos e1 _ e2) = do
t1 <- checkExpr e1
t2 <- checkExpr e2
case (t1, t2) of
(Int, Int) -> return Bool
(Bool, Bool) -> return Bool
_ -> tError ("Cannot compare " ++ show t1 ++ " and " ++ show t2) pos
checkExpr (Gram.EAdd pos e1 _ e2) = do
t1 <- checkExpr e1
t2 <- checkExpr e2
case (t1, t2) of
(Int, Int) -> return Int
(Str, Str) -> return Str
_ -> tError ("Cannot add " ++ show t1 ++ " and " ++ show t2) pos
checkExpr (Gram.EMul pos e1 _ e2) = do
t1 <- checkExpr e1
t2 <- checkExpr e2
case (t1, t2) of
(Int, Int) -> return Int
_ -> tError ("Cannot multiply " ++ show t1 ++ " and " ++ show t2) pos
checkExpr (Gram.EVar pos (Gram.Ident name)) = do
maybeType <- asks (Map.lookup name . types)
case maybeType of
Just t -> return t
Nothing -> tError (name ++ " is not defined") pos
checkExpr (Gram.EApp pos (Gram.Ident name) exprs) = do
maybeFunType <- asks (Map.lookup name . types)
case maybeFunType of
Nothing -> tError (name ++ " is not defined") pos
Just (Fun retType argTypes) -> do
-- check if number of arguments is not too big
if Prelude.length exprs > Prelude.length argTypes
then tError ("Too many arguments in " ++ name) pos
else do
-- check if types of arguments are correct
exprTypes <- mapM checkExpr exprs
-- because we don't have to pass all arguments, we have to check only the given ones
let argTypesToCheck = Prelude.take (Prelude.length exprs) argTypes
if exprTypes == argTypesToCheck
then return retType
else tError ("Wrong types of arguments in " ++ name ++ ": " ++ show exprTypes ++ " instead of " ++ show argTypesToCheck) pos
checkExpr (Gram.ENewList pos exprs) = do
exprTypes <- mapM checkExpr exprs
if Prelude.length exprTypes == 0
then tError ("Cannot create empty list") pos
else do
let firstType = Prelude.head exprTypes
if all (== firstType) exprTypes
then return (List firstType)
else tError ("Cannot create list of different types: " ++ show exprTypes) pos
checkExpr (Gram.EGetElem pos (Gram.Ident var) expr) = do
maybeVarType <- asks (Map.lookup var . types)
case maybeVarType of
Just (List t) -> do
exprType <- checkExpr expr
if exprType == Int
then return t
else tError ("Cannot get element of list with index of type " ++ show exprType) pos
_ -> tError (var ++ " is not a list") pos
checkExpr (Gram.Len pos (Gram.Ident var)) = do
maybeVarType <- asks (Map.lookup var . types)
case maybeVarType of
Just (List _) -> return Int
_ -> tError (var ++ " is not a list") pos
----------------- Arguments -----------------
getArgumentTypes :: [Gram.Arg] -> TypeMonad [(String, Type)]
getArgumentTypes [] = return []
getArgumentTypes ((Gram.ArType _ (Gram.Ident name) gtype):r) = do
rest <- getArgumentTypes r
return ((name, gtypeToType gtype):rest)
getArgumentTypes ((Gram.ArValue _ (Gram.Ident name) expr):r) = do
rest <- getArgumentTypes r
exprType <- checkExpr expr
return ((name, exprType):rest)
getArgumentTypes ((Gram.ArRef _ (Gram.Ident name) gtype):r) = do
rest <- getArgumentTypes r
return ((name, gtypeToType gtype):rest)
addArgumentTypesToEnv :: [(String, Type)] -> TEnv -> TEnv
addArgumentTypesToEnv [] env = env
addArgumentTypesToEnv ((name, typ):r) env = addArgumentTypesToEnv r (addVariableType name typ env)
----------------- Statements -----------------
-- Type to check if return statement is correct (if it exists)
checkStmts :: [Gram.Stmt] -> TypeMonad Type
checkStmts [] = return None
checkStmts ((Gram.Empty _):r) = checkStmts r
checkStmts ((Gram.Print pos e):r) = do
-- Everything is printable
checkExpr e
checkStmts r
checkStmts ((Gram.AssStmt pos target source):r) = do
case source of
-- right side: type => Declaration of new variable
(Gram.SourceType pos2 gtype) -> do
case target of
(Gram.TargetId pos1 (Gram.Ident var)) -> do -- when target is an Identifier
local (addVariableType var (gtypeToType gtype)) (checkStmts r)
(Gram.DummyTarget _) -> do -- when target is DummyTarget
tError ("cannot assign type " ++ (show gtype) ++ " to dummy target") pos
_ -> tError ("cannot declare new variable of type " ++ (show gtype) ++ " to " ++ (show target)) pos
-- right side: expression => Assignment to variable if exists, declaration of new variable if not
(Gram.SourceExpr pos2 expr) -> do
exprType <- checkExpr expr
case target of
(Gram.TargetId pos1 (Gram.Ident var)) -> do -- when target is an Identifier
-- check if variable is mutable
isMutable <- asks (Map.lookup var . mutable)
case isMutable of
Just True -> do
maybeVarType <- asks (Map.lookup var . types)
case maybeVarType of
Just varType -> do
if varType == exprType
then checkStmts r
else tError ("cannot assign type " ++ (show exprType) ++ " to " ++ var ++ " of type " ++ (show varType)) pos
-- create new variable
Nothing -> local (addVariableType var exprType) (checkStmts r)
Just False -> tError ("cannot assign to immutable variable " ++ var) pos
-- create new variable
Nothing -> local (addVariableType var exprType) (checkStmts r)
(Gram.TargetList pos1 (Gram.Ident var) expr) -> do -- when target is a list and index
-- evaluate index
indexType <- checkExpr expr
if indexType == Int
then do
maybeVarType <- asks (Map.lookup var . types)
case maybeVarType of
Just (List t) -> do
if t == exprType
then checkStmts r
else tError ("cannot assign type " ++ (show exprType) ++ " to " ++ var ++ " of type " ++ (show t)) pos
_ -> tError (var ++ " is not a list") pos
else tError ("cannot assign to list with index of non-int type " ++ (show indexType)) pos
(Gram.DummyTarget _) -> do -- when target is DummyTarget, do nothing
checkStmts r
-- return statement
checkStmts ((Gram.Ret pos expr):r) = do
maybeInFun <- asks in_function
case maybeInFun of
Nothing -> tError ("Return statement outside of function") pos
Just inFun -> do
maybeFunType <- asks (Map.lookup inFun . types)
case maybeFunType of
Nothing -> tError ("Function " ++ inFun ++ " is not defined") pos
Just (Fun retType _) -> do
exprType <- checkExpr expr
if retType == exprType
then return retType
else tError ("Cannot return type " ++ (show exprType) ++ " from function " ++ inFun ++ " of return type " ++ (show retType)) pos
checkStmts ((Gram.VRet pos):r) = do
maybeInFun <- asks in_function
case maybeInFun of
Nothing -> tError ("Return statement outside of function") pos
Just inFun -> do
maybeFunType <- asks (Map.lookup inFun . types)
case maybeFunType of
Nothing -> tError ("Function " ++ inFun ++ " is not defined") pos
Just (Fun retType _) -> do
if retType == None
then checkStmts r
else tError ("Cannot return nothing from function " ++ inFun ++ " of return type " ++ (show retType)) pos
checkStmts ((Gram.DeclFunc pos (Gram.FuncStmt _ (Gram.Ident funName) args retType (Gram.Blk _ bodyStmts))):r) = do
argumentTypes <- getArgumentTypes args
let funType = Fun (gtypeToType retType) (Prelude.map snd argumentTypes)
-- check if function body is correct, addVariableType and inFunction, and addArgumentTypesToEnv
returned <- local (addVariableType funName funType . inFunction funName . funcPos pos . addArgumentTypesToEnv argumentTypes) (checkStmts bodyStmts)
if returned == (gtypeToType retType)
then local (addVariableType funName funType) (checkStmts r)
else tError ("Function " ++ funName ++ " of return type " ++ (show (gtypeToType retType)) ++ " returns " ++ (show returned)) pos
checkStmts ((Gram.DeclFunc pos (Gram.VoidFuncStmt _ (Gram.Ident funName) args (Gram.Blk _ bodyStmts))):r) = do
argumentTypes <- getArgumentTypes args
let funType = Fun None (Prelude.map snd argumentTypes)
-- check if function body is correct, addVariableType and inFunction, and addArgumentTypesToEnv
returned <- local (addVariableType funName funType . inFunction funName . funcPos pos . addArgumentTypesToEnv argumentTypes) (checkStmts bodyStmts)
if returned == None
then local (addVariableType funName funType) (checkStmts r)
else tError ("Void function " ++ funName ++ " cannot return " ++ (show returned)) pos
checkStmts ((Gram.Cond pos expr (Gram.Blk _ bodyStmts)):r) = do
eType <- checkExpr expr
case eType of
Bool -> checkStmts bodyStmts >> checkStmts r
_ -> tError (show eType ++ " is not a boolean") pos
checkStmts ((Gram.CondElse pos expr (Gram.Blk _ bodyStmts1) (Gram.Blk _ bodyStmts2)):r) = do
eType <- checkExpr expr
case eType of
Bool -> checkStmts bodyStmts1 >> checkStmts bodyStmts2 >> checkStmts r
_ -> tError (show eType ++ " is not a boolean") pos
checkStmts ((Gram.VoidCall pos (Gram.Ident funName) exprs):r) = do
resType <- checkExpr (Gram.EApp pos (Gram.Ident funName) exprs)
checkStmts r
checkStmts ((Gram.While pos expr (Gram.Blk pos2 bodyStmts)):r) = do
eType <- checkExpr expr
case eType of
Bool -> do
local (inLoop True) (checkStmts bodyStmts)
checkStmts r
_ -> tError (show eType ++ " is not a boolean") pos
checkStmts ((Gram.For pos (Gram.Ident var) expr1 expr2 (Gram.Blk pos2 bodyStmts)):r) = do
eType1 <- checkExpr expr1
eType2 <- checkExpr expr2
case (eType1, eType2) of
(Int, Int) -> do
local (addImmutableVariableType var Int . inLoop True) (checkStmts bodyStmts)
checkStmts r
_ -> tError (show eType1 ++ " and " ++ show eType2 ++ " are not integers") pos
checkStmts ((Gram.Break pos):r) = do
inLoop <- asks in_loop
if inLoop
then checkStmts r
else tError "Break statement outside of while loop" pos
checkStmts ((Gram.Cont pos):r) = do
inLoop <- asks in_loop
if inLoop
then checkStmts r
else tError "Continue statement outside of while loop" pos
checkStmts ((Gram.Grow pos (Gram.Ident var) expr):r) = do
maybeVarType <- asks (Map.lookup var . types)
case maybeVarType of
Just (List t) -> do
exprType <- checkExpr expr
if exprType == t
then checkStmts r
else tError ("Cannot grow list of type " ++ show t ++ " with element of type " ++ show exprType) pos
_ -> tError (var ++ " is not a list") pos
checkStmts ((Gram.Cut pos (Gram.Ident var)):r) = do
maybeVarType <- asks (Map.lookup var . types)
case maybeVarType of
Just (List _) -> checkStmts r
_ -> tError (var ++ " is not a list") pos
----------------- Program -----------------
checkProgram :: Gram.Program -> TypeMonad ()
checkProgram (Gram.Prog pos stmts) =
checkStmts stmts >> return ()
--- Run program
runChecker :: Gram.Program -> Either String ()
runChecker prog =
let env = TEnv { types = fromList [],
mutable = fromList [],
in_function = Nothing,
func_pos = Nothing,
in_loop = False}
in runReader (runExceptT (checkProgram prog)) env