packages feed

language-lustre-1.0.0: Language/Lustre/TypeCheck/Monad.hs

{-# Language OverloadedStrings, GeneralizedNewtypeDeriving, DataKinds #-}
module Language.Lustre.TypeCheck.Monad where

import Data.Set(Set)
import qualified Data.Set as Set
import Data.Map(Map)
import qualified Data.Map as Map
import Data.Foldable(for_)
import Text.PrettyPrint as PP
import MonadLib

import Language.Lustre.Name
import Language.Lustre.AST
import Language.Lustre.Pretty
import Language.Lustre.Monad (LustreM, LustreError(..))
import qualified Language.Lustre.Monad as L
import Language.Lustre.Panic

-- | XXX: Parameterize so that we can startin in a non-empty environment.
runTC :: M a -> LustreM a
runTC m =
  do (a,_finS) <- runStateT rw0 $ runReaderT ro0 $ unM m
     -- L.logMessage "Clock subst:"
     -- dumpClockSubstLustre (rwClockVarSubst _finS)
     pure a
  where
  ro0 = RO { roConstants  = Map.empty
           , roUserNodes  = Map.empty
           , roIdents     = Map.empty
           , roCurRange   = []
           , roTypeNames  = Map.empty
           , roTemporal   = False
           , roUnsafe     = False
           }

  rw0 = RW { rwClockVarSubst = Map.empty
           , rwClockVars = Set.empty
           }




newtype M a = M { unM ::
  WithBase LustreM
    [ ReaderT RO
    , StateT  RW
    ] a
  } deriving (Functor,Applicative,Monad)

-- | Information about a node that can be called (i.e., is in scope)
data NodeInfo = NodeInfo
  { niName         :: Ident           -- ^ Definition site
  , niSafety       :: Safety          -- ^ Safe/unsafe
  , niType         :: NodeType        -- ^ Function/node
  , niStaticParams :: [StaticParam]   -- ^ Static parametres
  , niProfile      :: NodeProfile     -- ^ Inputs and ouputs
  }

data RO = RO
  { roConstants   :: Map OrigName (SourceRange, Type)
    -- ^ Constants that are in scope. These include top-level constants,
    -- constant (i.e., static) parameters, and local constants.

  , roUserNodes   :: Map OrigName NodeInfo
    -- ^ User defined nodes in scope, as well as static node parameters.

  , roIdents      :: Map OrigName (SourceRange, CType)
    -- ^ Locals in scope (i.e., arguments and node locals)

  , roTypeNames   :: Map OrigName (SourceRange, NamedType) -- no type vars here
    -- ^ Named types in scope (top level declarations plus static parameters)

  , roCurRange    :: [SourceRange]
    -- ^ The "path" of locations that lead us to where we currently are.

  , roTemporal    :: Bool
    -- ^ Are temporal constructs OK?

  , roUnsafe      :: Bool
    -- ^ Are unsafe constucts OK?
  }


data RW = RW
  { rwClockVarSubst  :: Map CVar IClock
  , rwClockVars      :: Set CVar
    -- ^ Clock variables in the current node.
    -- Ones that don't get bound are defaulted to the base clocks.
  }

data NamedType = StructTy [FieldType]
                 -- ^ Order of the fields should match declaration
               | EnumTy   (Set OrigName)
               | AliasTy  Type
               | AbstractTy


reportError :: Doc -> M a
reportError msg =
  M $ do rs <- roCurRange <$> ask
         inBase $ L.reportError $ TCError rs msg

notYetImplemented :: Doc -> M a
notYetImplemented f =
  reportError $ nestedError "XXX: Feature not yet implemented:"
                            [ "Feature:" <+> f ]

nestedError :: Doc -> [Doc] -> Doc
nestedError x ys = vcat (x : [ "***" <+> y | y <- ys ])

inRange :: SourceRange -> M a -> M a
inRange r (M a) = M (mapReader upd a)
  where upd ro = ro { roCurRange = r : roCurRange ro }

inRangeSet :: SourceRange -> M a -> M a
inRangeSet r (M a) = M (mapReader upd a)
  where upd ro = ro { roCurRange = [r] }

inRangeSetMaybe :: Maybe SourceRange -> M a -> M a
inRangeSetMaybe mb m = case mb of
                         Nothing -> m
                         Just r -> inRangeSet r m

inRangeMaybe :: Maybe SourceRange -> M a -> M a
inRangeMaybe mb m = case mb of
                      Nothing -> m
                      Just r  -> inRange r m

lookupLocal :: Ident -> M CType
lookupLocal i =
  do ro <- M ask
     let orig = identOrigName i
     case Map.lookup orig (roIdents ro) of
       Nothing -> panic "lookupLocal"
                            [ "Undefined identifier: " ++ showPP i ]
       Just (_,t) -> pure t


lookupConst :: Name -> M Type
lookupConst c =
  do ro <- M ask
     case Map.lookup (nameOrigName c) (roConstants ro) of
       Nothing    -> panic "lookupConst" [ "Undefined constant: " ++ showPP c ]
       Just (_,t) -> pure t


resolveNamed :: Name -> M Type
resolveNamed x =
  do ro <- M ask
     case Map.lookup (nameOrigName x) (roTypeNames ro) of
       Nothing -> panic "resolveNamed" [ "Undefined type:" ++ showPP x ]
       Just (_,nt) -> pure $ case nt of
                               AliasTy t -> t
                               _         -> NamedType x

lookupStruct :: Name -> M [FieldType]
lookupStruct s =
  do ro <- M ask
     case Map.lookup (nameOrigName s) (roTypeNames ro) of
       Nothing -> panic "lookupStruct" [ "Undefined struct: " ++ showPP s ]
       Just (_,nt) ->
         case nt of
           StructTy fs -> pure fs
           EnumTy {}   -> reportError $ nestedError
                          "Enumeration used where a struct was expected."
                          [ "Type:" <+> pp s ]
           AliasTy at ->
             case at of
               NamedType s' -> lookupStruct s'
               _ -> reportError $ nestedError
                    "Type is not a struct."
                    [ "Type name:" <+> pp s
                    , "Type definition:" <+> pp at
                    ]

           AbstractTy -> reportError $ nestedError
                          "Abstract type used where a struct was expected."
                          ["Name:" <+> pp s]


lookupNodeInfo :: Name -> M NodeInfo
lookupNodeInfo n =
  do ro <- M ask
     case Map.lookup (nameOrigName n) (roUserNodes ro) of
       Just info -> pure info
       Nothing -> panic "lookupNodeProfile" [ "Undefined node: " ++ showPP n ]

withConst :: Ident -> Type -> M a -> M a
withConst x t (M m) =
  do ro <- M ask
     let nm = identOrigName x
     let cs = roConstants ro
     M (local ro { roConstants = Map.insert nm (range x,t) cs } m)


withLocal :: Ident -> CType -> M a -> M a
withLocal i t (M m) =
  M $ do ro <- ask
         let is = roIdents ro
             nm = identOrigName i
         local ro { roIdents = Map.insert nm (range i, t) is } m

withNode :: NodeInfo -> M a -> M a
withNode ni (M m) =
  M $ do ro <- ask
         let nm = identOrigName (niName ni)
         local ro { roUserNodes = Map.insert nm ni (roUserNodes ro) } m

withNamedType :: Ident -> NamedType -> M a -> M a
withNamedType x t (M m) =
  M $ do ro <- ask
         let nm = identOrigName x
         local ro { roTypeNames = Map.insert nm (range x,t)
                                               (roTypeNames ro) } m


withLocals :: [(Ident,CType)] -> M a -> M a
withLocals xs k =
  case xs of
    []              -> k
    (x,t) : more -> withLocal x t (withLocals more k)

allowTemporal :: Bool -> M a -> M a
allowTemporal b (M m) = M (mapReader upd m)
  where upd ro = ro { roTemporal = b }

checkTemporalOk :: Doc -> M ()
checkTemporalOk msg =
  do ok <- M (roTemporal <$> ask)
     unless ok $
       reportError $ nestedError
       "Temporal operators are not allowed in a function."
       [ "Operator:" <+> msg ]

getTemporalLevel :: M NodeType
getTemporalLevel =
  do ok <- M (roTemporal <$> ask)
     pure (if ok then Node else Function)

allowUnsafe :: Bool -> M a -> M a
allowUnsafe b (M m) = M (mapReader upd m)
  where upd ro = ro { roUnsafe = b }

getUnsafeLevel :: M Safety
getUnsafeLevel =
  do ok <- M (roUnsafe <$> ask)
     pure (if ok then Unsafe else Safe)



-- | Generate a fresh clock variable.
newClockVar :: M IClock
newClockVar = M $
  do n <- inBase L.newInt
     let cv = CVar n
     sets_ $ \rw -> rw { rwClockVars = Set.insert cv (rwClockVars rw) }
     pure (ClockVar cv)


-- | Assumes that the clock is zonked
bindClockVar :: CVar -> IClock -> M ()
bindClockVar x c =
  case c of
    ClockVar y | x == y -> pure ()
    _ -> do let upd cl = case cl of
                           ClockVar i | i == x -> c
                           _ -> cl
            M $ sets_ $ \rw -> rw { rwClockVarSubst = Map.insert x c
                                                    $ fmap upd
                                                    $ rwClockVarSubst rw
                                  , rwClockVars = Set.delete x (rwClockVars rw)
                                  }

dumpClockSubst :: M ()
dumpClockSubst = M $
  do su <- rwClockVarSubst <$> get
     lift $ lift $ dumpClockSubstLustre su

dumpClockSubstLustre :: Map CVar IClock -> LustreM ()
dumpClockSubstLustre su =
  for_ (Map.toList su) $ \(x,v) ->
    L.logMessage (show (pp x <+> ":=" <+> pp v))

debugMessage :: String -> M ()
debugMessage s = M $ lift $ lift $ L.logMessage s


-- | Generate a new scope of clock variables.  Variables that are not defined
-- by the parameter computation will be defaulted to "base clock"
inClockScope :: M a -> M a
inClockScope (M m) = M $
  do old <- sets $ \rw -> (rwClockVars rw, rw { rwClockVars = Set.empty })
     a <- m
     leftover <- rwClockVars <$> get
     let mp = Map.fromList [ (x,BaseClock) | x <- Set.toList leftover ]
     sets_ $ \rw -> rw { rwClockVars = old
                       , rwClockVarSubst = Map.union mp (rwClockVarSubst rw)
                       }
     pure a



zonkClock :: IClock -> M IClock
zonkClock c =
  case c of
    BaseClock -> pure c
    KnownClock (WhenClock r v i) ->
       do v' <- zonkExpr v
          case isId v' of
            Just j | i == j -- clocks that are always true
                    -> pure BaseClock
            _ -> pure (KnownClock (WhenClock r v' i))
    ClockVar v -> M $ do su <- rwClockVarSubst <$> get
                         pure (Map.findWithDefault c v su)
  where
  isId e = case e of
             ERange _ e1 -> isId e1
             Const e' _ -> isId e'
             Var (Unqual x) -> Just x
             _ -> Nothing



-- | Apply the substitution to types in the AST.
-- Currently, only the 'Const' construct contains a type.
zonkExpr :: Expression -> M Expression
zonkExpr expr =
  case expr of
    ERange r e -> ERange r <$> zonkExpr e
    Const e ty -> Const <$> zonkExpr e <*> zonkCType ty
    Var {}     -> pure expr
    Lit {}     -> pure expr
    e `When` c -> When <$> zonkExpr e <*> zonkClockExpr c

    Tuple es            -> Tuple <$> traverse zonkExpr es
    Array es            -> Array <$> traverse zonkExpr es
    Select e s          -> Select <$> zonkExpr e <*> zonkSelector s
    Struct s fs         -> Struct s <$> traverse zonkField fs
    UpdateStruct s e fs -> UpdateStruct s
                            <$> zonkExpr e
                            <*> traverse zonkField fs
    WithThenElse e1 e2 e3 -> WithThenElse <$> zonkExpr e1 <*>
                                              zonkExpr e2 <*> zonkExpr e3
    Merge i as -> Merge i <$> traverse zonkMergeCase as
    Call f es c mTys -> Call f <$> traverse zonkExpr es <*> zonkClock c <*>
                            case mTys of
                                Nothing -> return Nothing
                                Just tys -> Just <$> mapM zonkCType tys

zonkCType :: CType -> M CType
zonkCType ct =
  do t <- zonkType (cType ct)
     c <- zonkClock (cClock ct)
     pure CType { cType = t, cClock = c }

zonkType :: Type -> M Type
zonkType t =
  case t of
    ArrayType elT sz -> ArrayType <$> zonkType elT <*> zonkExpr sz
    IntSubrange e1 e2 -> IntSubrange <$> zonkExpr e1 <*> zonkExpr e2
    NamedType {} -> pure t
    RealType -> pure t
    IntType -> pure t
    BoolType -> pure t
    TypeRange r t' -> TypeRange r <$> zonkType t'

zonkField :: Field Expression -> M (Field Expression)
zonkField f =
  do e <- zonkExpr (fValue f)
     pure f { fValue = e }

zonkMergeCase :: MergeCase Expression -> M (MergeCase Expression)
zonkMergeCase (MergeCase k e) = MergeCase <$> zonkExpr k <*> zonkExpr e

zonkSelector :: Selector Expression -> M (Selector Expression)
zonkSelector sel =
  case sel of
    SelectField {} -> pure sel
    SelectElement e -> SelectElement <$> zonkExpr e
    SelectSlice e   -> SelectSlice <$> zonkSlice e

zonkSlice :: ArraySlice Expression -> M (ArraySlice Expression)
zonkSlice a =
  do s <- zonkExpr (arrayStart a)
     e <- zonkExpr (arrayEnd a)
     t <- traverse zonkExpr (arrayStep a)
     pure ArraySlice { arrayStart = s, arrayEnd = e, arrayStep = t }

zonkClockExpr :: ClockExpr -> M ClockExpr
zonkClockExpr (WhenClock r e i) =
  do e' <- zonkExpr e
     pure (WhenClock r e' i)

zonkBody :: NodeBody -> M NodeBody
zonkBody b =
  do eqs <- traverse zonkEqn (nodeEqns b)
     pure b { nodeEqns = eqs }

zonkEqn :: Equation -> M Equation
zonkEqn eqn =
  case eqn of
    Assert p ty e -> Assert p ty <$> zonkExpr e
    Property p e -> Property p <$> zonkExpr e
    IsMain {} -> pure eqn
    IVC {} -> pure eqn
    Realizable {} -> pure eqn
    Define lhs e -> Define lhs <$> zonkExpr e

zonkContract :: Contract -> M Contract
zonkContract c =
  do cis <- mapM zonkContractItem (contractItems c)
     pure c { contractItems = cis }

zonkContractItem :: ContractItem -> M ContractItem
zonkContractItem ci =
  case ci of
    Assume l e    -> Assume l    <$> zonkExpr e
    Guarantee l e -> Guarantee l <$> zonkExpr e
    _ -> panic "zonkContractItem" ["unsupported contract item"]