module Language.Clafer.Intermediate.ResolverType (resolveTModule, intersects, (+++), fromUnionType, unionType) where
import Prelude hiding (exp)
import Language.ClaferT
import Language.Clafer.Common
import Language.Clafer.Intermediate.Analysis
import Language.Clafer.Intermediate.Intclafer hiding (uid)
import qualified Language.Clafer.Intermediate.Intclafer as I
import Control.Applicative
import Control.Exception (assert)
import Control.Monad.Error
import Control.Monad.List
import Control.Monad.Reader
import Data.Either
import Data.List
import Data.Maybe
type TypeDecls = [(String, IType)]
data TypeInfo = TypeInfo {iTypeDecls::TypeDecls, iInfo::Info, iCurThis::SClafer, iCurPath::Maybe IType}
newtype TypeAnalysis a = TypeAnalysis (ReaderT TypeInfo (Either ClaferSErr) a)
deriving (MonadError ClaferSErr, Monad, Functor, MonadReader TypeInfo, Applicative)
typeOfUid :: MonadTypeAnalysis m => String -> m IType
typeOfUid uid = (fromMaybe (TClafer [uid]) . lookup uid) <$> typeDecls
class MonadAnalysis m => MonadTypeAnalysis m where
curThis :: m SClafer
localCurThis :: SClafer -> m a -> m a
curPath :: m (Maybe IType)
localCurPath :: IType -> m a -> m a
typeDecls :: m TypeDecls
localDecls :: TypeDecls -> m a -> m a
instance MonadTypeAnalysis TypeAnalysis where
curThis = TypeAnalysis $ asks iCurThis
localCurThis newThis (TypeAnalysis d) =
TypeAnalysis $ local setCurThis d
where
setCurThis t = t{iCurThis = newThis}
curPath = TypeAnalysis $ asks iCurPath
localCurPath newPath (TypeAnalysis d) =
TypeAnalysis $ local setCurPath d
where
setCurPath t = t{iCurPath = Just newPath}
typeDecls = TypeAnalysis $ asks iTypeDecls
localDecls extra (TypeAnalysis d) =
TypeAnalysis $ local addTypeDecls d
where
addTypeDecls t@TypeInfo{iTypeDecls = c} = t{iTypeDecls = extra ++ c}
instance MonadTypeAnalysis m => MonadTypeAnalysis (ListT m) where
curThis = lift $ curThis
localCurThis = mapListT . localCurThis
curPath = lift $ curPath
localCurPath = mapListT . localCurPath
typeDecls = lift typeDecls
localDecls = mapListT . localDecls
instance MonadTypeAnalysis m => MonadTypeAnalysis (ErrorT ClaferSErr m) where
curThis = lift $ curThis
localCurThis = mapErrorT . localCurThis
curPath = lift $ curPath
localCurPath = mapErrorT . localCurPath
typeDecls = lift typeDecls
localDecls = mapErrorT . localDecls
instance MonadAnalysis TypeAnalysis where
clafers = asks (sclafers . iInfo)
withClafers cs r =
local setInfo r
where
setInfo t = t{iInfo = Info cs}
runTypeAnalysis :: TypeAnalysis a -> IModule -> Either ClaferSErr a
runTypeAnalysis (TypeAnalysis tc) imodule = runReaderT tc $ TypeInfo [] (gatherInfo imodule) undefined Nothing
unionType :: IType -> [String]
unionType TString = ["string"]
unionType TReal = ["real"]
unionType TInteger = ["integer"]
unionType TBoolean = ["boolean"]
unionType (TClafer u) = u
(+++) :: IType -> IType -> IType
t1 +++ t2 = fromJust $ fromUnionType $ unionType t1 ++ unionType t2
fromUnionType :: [String] -> Maybe IType
fromUnionType u =
case sort $ nub $ u of
["string"] -> return TString
["real"] -> return TReal
["integer"] -> return TInteger
["int"] -> return TInteger
["boolean"] -> return TBoolean
[] -> Nothing
u' -> return $ TClafer u'
closure :: MonadAnalysis m => [String] -> m [String]
closure ut = concat <$> mapM hierarchy ut
intersection :: MonadAnalysis m => IType -> IType -> m (Maybe IType)
intersection t1 t2 =
do
h1 <- mapM hierarchy $ unionType t1
h2 <- mapM hierarchy $ unionType t2
let ut = catMaybes [contains (head u1) u2 `mplus` contains (head u2) u1 | u1 <- h1, u2 <- h2]
return $ fromUnionType ut
where
contains i is = if i `elem` is then Just i else Nothing
intersects :: MonadAnalysis m => IType -> IType -> m Bool
intersects t1 t2 = isJust <$> intersection t1 t2
numeric :: IType -> Bool
numeric TReal = True
numeric TInteger = True
numeric _ = False
coerce :: IType -> IType -> IType
coerce TReal TReal = TReal
coerce TReal TInteger = TReal
coerce TInteger TReal = TReal
coerce TInteger TInteger = TInteger
coerce x y = error $ "Not numeric: " ++ show x ++ ", " ++ show y
str :: IType -> String
str t =
case unionType t of
[t'] -> t'
ts -> "[" ++ intercalate "," ts ++ "]"
getIfThenElseType :: MonadAnalysis m => IType -> IType -> m (Maybe IType)
getIfThenElseType t1 t2 =
do
h1 <- mapM hierarchy $ unionType t1
h2 <- mapM hierarchy $ unionType t2
let ut = catMaybes [commonHierarchy u1 u2 | u1 <- h1, u2 <- h2]
return $ fromUnionType ut
where
commonHierarchy h1 h2 = filterClafer $ commonHierarchy' (reverse h1) (reverse h2) Nothing
commonHierarchy' (x:xs) (y:ys) accumulator =
if (x == y)
then
if (null xs || null ys)
then Just x
else commonHierarchy' xs ys $ Just x
else accumulator
commonHierarchy' _ _ _ = error "Function commonHierarchy' from ResolverType expects two non empty lists but was given at least one empty list!"
filterClafer value =
if (value == Just "clafer") then Nothing else value
resolveTModule :: (IModule, GEnv) -> Either ClaferSErr IModule
resolveTModule (imodule, _) =
case runTypeAnalysis (analysis $ _mDecls imodule) imodule of
Right mDecls' -> return imodule{_mDecls = mDecls'}
Left err -> throwError err
where
analysis decls1 = mapM (resolveTElement $ rootUid) decls1
resolveTElement :: String -> IElement -> TypeAnalysis IElement
resolveTElement _ (IEClafer iclafer) =
do
elements' <- mapM (resolveTElement $ I._uid iclafer) (_elements iclafer)
return $ IEClafer iclafer{_elements = elements'}
resolveTElement parent' (IEConstraint _isHard _pexp) =
IEConstraint _isHard <$> (testBoolean =<< resolveTConstraint parent' _pexp)
where
testBoolean pexp' =
do
unless (typeOf pexp' == TBoolean) $
throwError $ SemanticErr (_inPos pexp') ("Cannot construct constraint on type '" ++ str (typeOf pexp') ++ "'")
return pexp'
resolveTElement parent' (IEGoal isMaximize' pexp') =
IEGoal isMaximize' <$> resolveTConstraint parent' pexp'
resolveTConstraint :: String -> PExp -> TypeAnalysis PExp
resolveTConstraint curThis' constraint =
do
curThis'' <- claferWithUid curThis'
head <$> (localCurThis curThis'' $ (resolveTPExp constraint :: TypeAnalysis [PExp]))
resolveTPExp :: PExp -> TypeAnalysis [PExp]
resolveTPExp p =
do
x <- resolveTPExp' p
case partitionEithers x of
(f:_, []) -> throwError f
([], []) -> assert False $ error "No results but no errors."
(_, xs) -> return xs
resolveTPExp' :: PExp -> TypeAnalysis [Either ClaferSErr PExp]
resolveTPExp' p@PExp{_inPos, _exp = IClaferId{_sident = "ref"}} =
runListT $ runErrorT $ do
curPath' <- curPath
case curPath' of
Just curPath'' -> do
ut <- closure $ unionType curPath''
t <- runListT $ refOf =<< foreachM ut
case fromUnionType t of
Just t' -> return $ p `withType` t'
Nothing -> throwError $ SemanticErr _inPos ("Cannot ref from type '" ++ str curPath'' ++ "'")
Nothing -> throwError $ SemanticErr _inPos ("Cannot ref at the start of a path")
resolveTPExp' p@PExp{_inPos, _exp = IClaferId{_sident = "parent"}} =
runListT $ runErrorT $ do
curPath' <- curPath
case curPath' of
Just curPath'' -> do
parent' <- fromUnionType <$> runListT (parentOf =<< liftList (unionType curPath''))
when (isNothing parent') $
throwError $ SemanticErr _inPos "Cannot parent from root"
let result = p `withType` fromJust parent'
return result
<++>
addRef result
Nothing -> throwError $ SemanticErr _inPos "Cannot parent at the start of a path"
resolveTPExp' p@PExp{_exp = IClaferId{_sident = "integer"}} = runListT $ runErrorT $ return $ p `withType` TInteger
resolveTPExp' p@PExp{_inPos, _exp = IClaferId{_sident}} =
runListT $ runErrorT $ do
curPath' <- curPath
sident' <- if _sident == "this" then uid <$> curThis else return _sident
when (isJust curPath') $ do
c <- mapM (isChild sident') $ unionType $ fromJust curPath'
unless (or c) $ throwError $ SemanticErr _inPos ("'" ++ sident' ++ "' is not a child of type '" ++ str (fromJust curPath') ++ "'")
result <- (p `withType`) <$> typeOfUid sident'
return result
<++>
addRef result
resolveTPExp' p@PExp{_inPos, _exp} =
runListT $ runErrorT $ do
(iType', exp') <- ErrorT $ ListT $ resolveTExp _exp
return p{_iType = Just iType', _exp = exp'}
where
resolveTExp :: IExp -> TypeAnalysis [Either ClaferSErr (IType, IExp)]
resolveTExp e@(IInt _) = runListT $ runErrorT $ return (TInteger, e)
resolveTExp e@(IDouble _) = runListT $ runErrorT $ return (TReal, e)
resolveTExp e@(IStr _) = runListT $ runErrorT $ return (TString, e)
resolveTExp e@IFunExp {_op, _exps = [arg]} =
runListT $ runErrorT $ do
arg' <- lift $ ListT $ resolveTPExp arg
let t = typeOf arg'
let test c =
unless c $
throwError $ SemanticErr _inPos ("Function '" ++ _op ++ "' cannot be performed on " ++ _op ++ " '" ++ str t ++ "'")
let result
| _op == iNot = test (t == TBoolean) >> return TBoolean
| _op == iCSet = return TInteger
| _op == iSumSet = test (t == TInteger) >> return TInteger
| _op `elem` [iMin, iGMin, iGMax] = test (numeric t) >> return t
| otherwise = assert False $ error $ "Unknown op '" ++ _op ++ "'"
result' <- result
return (result', e{_exps = [arg']})
resolveTExp e@IFunExp {_op = ".", _exps = [arg1, arg2]} =
do
runListT $ runErrorT $ do
arg1' <- lift $ ListT $ resolveTPExp arg1
localCurPath (typeOf arg1') $ do
arg2' <- liftError $ lift $ ListT $ resolveTPExp arg2
return (fromJust $ _iType arg2', e{_exps = [arg1', arg2']})
resolveTExp e@IFunExp {_op = "++", _exps = [arg1, arg2]} =
do
arg1s' <- resolveTPExp arg1
arg2s' <- resolveTPExp arg2
let union' a b = typeOf a +++ typeOf b
return $ [return (union' arg1' arg2', e{_exps = [arg1', arg2']}) | (arg1', arg2') <- sortBy (comparing $ length . unionType . uncurry union') $ liftM2 (,) arg1s' arg2s']
resolveTExp e@IFunExp {_op, _exps = [arg1, arg2]} =
runListT $ runErrorT $ do
arg1' <- lift $ ListT $ resolveTPExp arg1
arg2' <- lift $ ListT $ resolveTPExp arg2
let t1 = typeOf arg1'
let t2 = typeOf arg2'
let testIntersect e1 e2 =
do
it <- intersection e1 e2
case it of
Just it' -> return it'
Nothing -> throwError $ SemanticErr _inPos ("Function '" ++ _op ++ "' cannot be performed on '" ++ str t1 ++ "' " ++ _op ++ " '" ++ str t2 ++ "'")
let testNotSame e1 e2 =
when (e1 `sameAs` e2) $
throwError $ SemanticErr _inPos ("Function '" ++ _op ++ "' is redundant because the two subexpressions are always equivalent")
let test c =
unless c $
throwError $ SemanticErr _inPos ("Function '" ++ _op ++ "' cannot be performed on '" ++ str t1 ++ "' " ++ _op ++ " '" ++ str t2 ++ "'")
let result
| _op `elem` logBinOps = test (t1 == TBoolean && t2 == TBoolean) >> return TBoolean
| _op `elem` [iLt, iGt, iLte, iGte] = test (numeric t1 && numeric t2) >> return TBoolean
| _op `elem` [iEq, iNeq] = testNotSame arg1' arg2' >> testIntersect t1 t2 >> return TBoolean
| _op == iDifference = testNotSame arg1' arg2' >> testIntersect t1 t2 >> return t1
| _op == iIntersection = testNotSame arg1' arg2' >> testIntersect t1 t2
| _op `elem` [iDomain, iRange] = testIntersect t1 t2
| _op `elem` relSetBinOps = testIntersect t1 t2 >> return TBoolean
| _op `elem` [iSub, iMul, iDiv] = test (numeric t1 && numeric t2) >> return (coerce t1 t2)
| _op == iPlus =
(test (t1 == TString && t2 == TString) >> return TString)
`catchError`
const (test (numeric t1 && numeric t2) >> return (coerce t1 t2))
| otherwise = error $ "ResolverType: Unknown op: " ++ show e
result' <- result
return (result', e{_exps = [arg1', arg2']})
resolveTExp e@(IFunExp "=>else" [arg1, arg2, arg3]) =
runListT $ runErrorT $ do
arg1' <- lift $ ListT $ resolveTPExp arg1
arg2' <- lift $ ListT $ resolveTPExp arg2
arg3' <- lift $ ListT $ resolveTPExp arg3
let t1 = typeOf arg1'
let t2 = typeOf arg2'
let t3 = typeOf arg3'
unless (t1 == TBoolean) $
throwError $ SemanticErr _inPos ("Function 'if/else' cannot be performed on 'if' " ++ str t1 ++ " 'then' " ++ str t2 ++ " 'else' " ++ str t3)
it <- getIfThenElseType t2 t3
t <- case it of
Just it' -> return it'
Nothing -> throwError $ SemanticErr _inPos ("Function '=>else' cannot be performed on if '" ++ str t1 ++ "' then '" ++ str t2 ++ "' else '" ++ str t3 ++ "'")
return (t, e{_exps = [arg1', arg2', arg3']})
resolveTExp e@IDeclPExp{_oDecls, _bpexp} =
runListT $ runErrorT $ do
oDecls' <- mapM resolveTDecl _oDecls
let extraDecls = [(decl, typeOf $ _body oDecl) | oDecl <- oDecls', decl <- _decls oDecl]
localDecls extraDecls $ do
bpexp' <- liftError $ lift $ ListT $ resolveTPExp _bpexp
return $ (TBoolean, e{_oDecls = oDecls', _bpexp = bpexp'})
where
resolveTDecl d@IDecl{_body} =
do
body' <- lift $ ListT $ resolveTPExp _body
return $ d{_body = body'}
resolveTExp e = error $ "Unknown iexp: " ++ show e
addRef :: PExp -> ErrorT ClaferSErr (ListT TypeAnalysis) PExp
addRef pexp =
do
localCurPath (typeOf pexp) $ do
deref <- (ErrorT $ ListT $ resolveTPExp' $ newPExp $ IClaferId "" "ref" False) `catchError` const (lift mzero)
let result = (newPExp $ IFunExp "." [pexp, deref]) `withType` typeOf deref
return result <++> addRef result
where
newPExp = PExp Nothing "" $ _inPos pexp
typeOf :: PExp -> IType
typeOf pexp = fromMaybe (error "No type") $ _iType pexp
withType :: PExp -> IType -> PExp
withType p t = p{_iType = Just t}
(<++>) :: (Error e, MonadPlus m) => ErrorT e m a -> ErrorT e m a -> ErrorT e m a
(ErrorT a) <++> (ErrorT b) = ErrorT $ a `mplus` b
liftError :: (MonadError e m, Error e) => ErrorT e m a -> ErrorT e m a
liftError e =
liftCatch catchError e throwError
where
liftCatch catchError' m h = ErrorT $ runErrorT m `catchError'` (runErrorT . h)