Skip to content
Snippets Groups Projects
TypeChecker.hs 3.24 KiB
Newer Older
  • Learn to ignore specific revisions
  • {-# LANGUAGE OverloadedStrings #-}
    module LogicIR.TypeChecker (typeCheck) where
    
    import Control.Monad (unless)
    
    import LogicIR.Expr
    import LogicIR.Parser ()
    
    -- | Type-check given expression.
    typeCheck :: LExpr -> Either String LExpr
    typeCheck lexp = typeOf lexp >> return lexp
    
    -- | Get the type of an expression or a type error.
    typeOf :: LExpr -> Either String Type
    typeOf lexp = case lexp of
      LVar (Var t _) -> return t
      LConst c       -> case c of
        CBool _ -> return "bool"
        CInt _  -> return "int"
        CReal _ -> return "real"
        CNil    -> fail "Type-checking before preprocessing null checks"
      LArray (Var t _) e -> case t of
        TArray t' -> do
          te <- typeOf e
          te == "int" ?? "(indexing): non-integer index"
          return t'
        _ -> fail "(indexing): non-array variable"
      LIsnull (Var t _) -> do
        isArray t ??  "(isNull): non-array argument"
        return "bool"
      LLen (Var t _) -> do
        isArray t ??  "(length): non-array argument"
        return "int"
      LUnop o e -> do
        t <- typeOf e
        case o of
          NNeg -> do
            isNum t ?? "(-): non-numeric argument"
            return t
          LNot -> do
            (t == "bool") ?? "(!): non-boolean argument"
            return t
      LBinop e o e' -> do
        t <- typeOf e
        t' <- typeOf e'
        case o of
          NAdd -> do
            isNum t && isNum t' ?? "(+): non-numeric arguments"
            return $ coerce t t'
          NSub -> do
            isNum t && isNum t' ?? "(-): non-numeric arguments"
            return $ coerce t t'
          NMul -> do
            isNum t && isNum t' ?? "(*): non-numeric arguments"
            return $ coerce t t'
          NDiv -> do
            isNum t && isNum t' ?? "(/): non-numeric arguments"
            return $ coerce t t'
          NRem -> do
            isNum t && isNum t' ?? "(%): non-numeric arguments"
            return $ coerce t t'
          CEqual -> do
            isNum t && isNum t' ?? "(==): non-numeric arguments"
            return "bool"
          CLess -> do
            isNum t && isNum t' ?? "(<): non-numeric arguments"
            return "bool"
          CGreater -> do
            isNum t && isNum t' ?? "(>): non-numeric arguments"
            return "bool"
          LAnd -> do
            t == "bool" && t' == "bool" ?? "(&&): non-boolean arguments"
            return "bool"
          LOr -> do
            t == "bool" && t' == "bool" ?? "(||): non-boolean arguments"
            return "bool"
          LImpl -> do
            t == "bool" && t' == "bool" ?? "(==>): non-boolean arguments"
            return "bool"
          LEqual -> do
            t == "bool" && t' == "bool" ?? "(<==>): non-boolean arguments"
            return "bool"
      LIf c e e'     -> do
        ct <- typeOf c
        ct == "bool" ?? "(if): non-boolean guard"
        t <- typeOf e
        t' <- typeOf e'
        t == t' ?? "(if): branches of different type"
        return t
      LQuant _ _ d e -> do
        dt <- typeOf d
        t <- typeOf e
        dt == "bool" ?? "(quantifier): non-boolean domain [" ++ show dt ++ "]"
        t == "bool" ?? "(quantifier): non-boolean body"
        return "bool"
      where
        infix 2 ??
        (??) :: Bool -> String -> Either String ()
        predicate ?? err = unless predicate $ fail err
        isNum :: Type -> Bool
        isNum t = t `elem` ["int", "real"]
        isArray :: Type -> Bool
        isArray (TArray _) = True
        isArray _          = False
        coerce :: Type -> Type -> Type
        coerce t t' = if "real" `elem` [t, t'] then "real" else "int"