module Il.Typechecker where
import Defs.Common
import Il.AST
import Control.Monad.State
import Data.Maybe
-- | Variables with types, return value for a function, other declared functions
data TypeState = TS {
getStateReturn :: Type,
getStateFunctions :: [Function],
getStateVariables :: [(VariableName, Type)] }
-- | State monad to remember the 'TypeState'
type Typechecker a = State TypeState a
-- | Typecheck a term block
typecheckB :: [Term] -> Typechecker [Type]
typecheckB = mapM typecheckT
-- | Assert a type to a term, raise an error otherwise
assertType :: Term -> Type -> Typechecker ()
assertType t1 tp2 = do
tp1' <- typecheckT t1
case tp1' of
TVoid -> error $ "Type error: " ++ show t1 ++ " does not return a value, should return " ++ show tp2
tp1 -> when (tp1 /= tp2) (error $ "Type error: " ++ show t1 ++ " is type " ++ show tp1 ++ ", should be" ++ show tp2)
-- | Typecheck a function definition
typecheckF :: [Function] -> Function -> [Type]
typecheckF fns (Function _ tp args (Block body)) = evalState (typecheckB body) (TS tp (dsinfFunctions ++ fns) args)
typecheckF _ (Function name _ _ _) = error $ "Function definition for " ++ show name ++ " is not a code block"
-- | Typecheck a function call
typecheckFC :: FunctionName -> [Term] -> Typechecker Type
typecheckFC f ts = do
s <- get
tps <- typecheckB ts
let fns = filter (\x -> getFunName x == f && tps == map snd (getFunArgs x)) (getStateFunctions s) --FIXME add safe lookup
case fns of
[] -> error $ "No matching function: " ++ show f
fn:[] -> return $ getFunType fn
fn:fnss -> error $ "More than one function matching: " ++ show (fn:fnss)
-- | Typecheck one field of a record
typecheckR :: (String, Term) -> Typechecker (String, Type)
typecheckR (n, t) = do
tp <- typecheckT t
case tp of
TVoid -> error $ "Type error: " ++ show t ++ " returning no value in a record field"
tp1 -> return (n, tp1)
-- | Typecheck a Dec or Inc operation
typecheckIncDec :: VariableName -> Typechecker (Type)
typecheckIncDec v = do
s <- get
let tcx = getStateVariables s
case lookup v tcx of
Just TVoid -> error $ "Type error: " ++ show v ++ " is not initilized, should be initialized as " ++ show TInt
Just TInt -> return TInt
tp -> error $ "Type error: " ++ show v ++ " has type " ++ show tp ++ "instead of " ++ show TInt
-- | Typecheck a term, 'Nothing' symbolizes the expressions without a value
typecheckT :: Term -> Typechecker (Type)
typecheckT (And t1 t2) = logOp t1 t2
typecheckT (Assign v t1) = do
s <- get
let tcx = getStateVariables s
case lookup v tcx of
Just tp -> do
assertType t1 tp
return TVoid
Nothing -> error $ "Variable " ++ show v ++ " not initialized"
typecheckT (Block ts) = do
_ <- typecheckB ts
return TVoid
typecheckT (Inc v) = typecheckIncDec v
typecheckT (Dec v) = typecheckIncDec v
typecheckT (Div t1 t2) = mathOp t1 t2
typecheckT (Eq t1 t2) = relOp t1 t2
typecheckT (For t1 t2 t3 t4) = do
_ <- typecheckT t1
assertType t2 TBool
_ <- typecheckT t3
_ <- typecheckT t4
return TVoid
typecheckT (Funcall f ts) = typecheckFC f ts
typecheckT (Geq t1 t2) = relOp t1 t2
typecheckT (Gt t1 t2) = relOp t1 t2
typecheckT (If t1 t2 t3) = do
assertType t1 TBool
_ <- typecheckT t2
_ <- typecheckT t3
return TVoid
typecheckT (Int _) = return TInt
typecheckT (InitAssign v t tp) = do
assertType t tp
s <- get
let tcx = getStateVariables s
case lookup v tcx of
Just tp1 -> error $ "Variable " ++ show v ++ " already initialized with type " ++ show tp1
Nothing -> do
put $ TS (getStateReturn s) (getStateFunctions s) ((v,tp):tcx)
return TVoid
typecheckT (Leq t1 t2) = relOp t1 t2
typecheckT (Lt t1 t2) = relOp t1 t2
typecheckT (Mul t1 t2) = mathOp t1 t2
typecheckT (Not t1) = do
assertType t1 TBool
return TBool
typecheckT (Or t1 t2) = logOp t1 t2
typecheckT (Record rs) = fmap (TRec) (mapM typecheckR rs)
typecheckT (Return t) = do
s <- get
let rt = getStateReturn s
assertType t rt
return TVoid
typecheckT (Sub t1 t2) = mathOp t1 t2
typecheckT (Sum t1 t2) = mathOp t1 t2
typecheckT (While t1 t2) = do
assertType t1 TBool
_ <- typecheckT t2
return TVoid
typecheckT (Var v) = do
s <- get
let tcx = getStateVariables s
case lookup v tcx of
Nothing -> error $ "Variable " ++ show v ++ " not initialized"
Just tp -> return tp
typecheckT (VarInit v tp) = do
s <- get
let tcx = getStateVariables s
case lookup v tcx of
Just tp1 -> error $ "Variable " ++ show v ++ " already initialized with type " ++ show tp1
Nothing -> do
put $ TS (getStateReturn s) (getStateFunctions s) ((v,tp):tcx)
return TVoid
relOp :: Term -> Term -> Typechecker (Type)
relOp t1 t2 = do
assertType t1 TInt
assertType t2 TInt
return TBool
mathOp :: Term -> Term -> Typechecker (Type)
mathOp t1 t2 = do
assertType t1 TInt
assertType t2 TInt
return TInt
logOp :: Term -> Term -> Typechecker (Type)
logOp t1 t2 = do
assertType t1 TBool
assertType t2 TBool
return TBool