agda2hs-1.2: src/Agda2Hs/Compile/Utils.hs
module Agda2Hs.Compile.Utils where
import Control.Arrow ( Arrow((***)) )
import Control.Monad.Except
import Control.Monad.Reader
import Control.Monad.Writer ( tell )
import Control.Monad.State ( put, modify )
import Data.Maybe ( isJust )
import qualified Data.Map as M
import qualified Language.Haskell.Exts as Hs
import Agda.Compiler.Backend hiding ( Args )
import Agda.Syntax.Common
import qualified Agda.Syntax.Concrete.Name as C
import Agda.Syntax.Internal
import Agda.Syntax.Position ( noRange )
import Agda.Syntax.Scope.Base
import Agda.Syntax.Scope.Monad ( bindVariable, freshConcreteName, isDatatypeModule )
import Agda.Syntax.Common.Pretty ( prettyShow )
import qualified Agda.Syntax.Common.Pretty as P
import Agda.TypeChecking.CheckInternal ( infer )
import Agda.TypeChecking.Constraints ( noConstraints )
import Agda.TypeChecking.Conversion ( equalTerm )
import Agda.TypeChecking.InstanceArguments ( findInstance )
import Agda.TypeChecking.Level ( isLevelType )
import Agda.TypeChecking.MetaVars ( newInstanceMeta )
import Agda.TypeChecking.Monad.SizedTypes ( isSizeType )
import Agda.TypeChecking.Pretty ( Doc, (<+>), text, PrettyTCM(..) )
import Agda.TypeChecking.Records ( isRecordConstructor, getRecordOfField )
import Agda.TypeChecking.Reduce ( instantiate, reduce )
import Agda.TypeChecking.Substitute ( Subst, TelV(TelV), Apply(..) )
import Agda.TypeChecking.Telescope ( telView )
import Agda.Utils.Lens ( (<&>) )
import Agda.Utils.Maybe
import Agda.Utils.Monad
import Agda.Utils.Singleton
import AgdaInternals
import Agda2Hs.AgdaUtils ( (~~) )
import Agda2Hs.Compile.Types
import Agda2Hs.HsUtils
import Agda2Hs.Pragma
concatUnzip :: [([a], [b])] -> ([a], [b])
concatUnzip = (concat *** concat) . unzip
infixr 0 /\, \/
(/\), (\/) :: (a -> Bool) -> (a -> Bool) -> a -> Bool
f /\ g = \x -> f x && g x
f \/ g = \x -> f x || g x
showTCM :: PrettyTCM a => a -> C String
showTCM x = liftTCM $ show <$> prettyTCM x
setCurrentRangeQ :: QName -> C a -> C a
setCurrentRangeQ = setCurrentRange . nameBindingSite . qnameName
isInScopeUnqualified :: QName -> C Bool
isInScopeUnqualified x = lift $ do
ys <- inverseScopeLookupName' AmbiguousAnything x <$> getScope
return $ any (not . C.isQualified) ys
freshString :: String -> C String
freshString s = liftTCM $ do
scope <- getScope
ctxNames <- map (prettyShow . nameConcrete) <$> getContextNames
return $ head $ filter (isFresh scope ctxNames) $ s : map (\i -> s ++ show i) [0..]
where
dummyName s = C.QName $ C.Name noRange C.NotInScope $ singleton $ C.Id s
isFresh scope ctxNames s =
null (scopeLookup (dummyName s) scope :: [AbstractName]) &&
not (s `elem` ctxNames)
makeList :: C Doc -> Term -> C [Term]
makeList = makeList' "Agda.Builtin.List.List.[]" "Agda.Builtin.List.List._∷_"
makeList' :: String -> String -> C Doc -> Term -> C [Term]
makeList' nil cons err v = do
v <- reduce v
case v of
Con c _ es
| [] <- vis es, conName c ~~ nil -> return []
| [x, xs] <- vis es, conName c ~~ cons -> (x :) <$> makeList' nil cons err xs
_ -> genericDocError =<< err
where
vis es = [ unArg a | Apply a <- es, visible a ]
makeListP' :: String -> String -> C Doc -> DeBruijnPattern -> C [DeBruijnPattern]
makeListP' nil cons err p = do
case p of
ConP c _ ps
| [] <- vis ps, conName c ~~ nil -> return []
| [x, xs] <- vis ps, conName c ~~ cons -> (x :) <$> makeListP' nil cons err xs
_ -> genericDocError =<< err
where
vis ps = [ namedArg p | p <- ps, visible p ]
-- When going under a binder we need to update the scope as well as the context in order to get
-- correct printing of variable names (Issue #14).
bindVar :: Int -> C ()
bindVar i = do
x <- nameOfBV i
liftTCM $ bindVariable LambdaBound (nameConcrete x) x
underAbstr :: Subst a => Dom Type -> Abs a -> (a -> C b) -> C b
underAbstr = underAbstraction' KeepNames
underAbstr_ :: Subst a => Abs a -> (a -> C b) -> C b
underAbstr_ = underAbstr __DUMMY_DOM__
-- Determine whether an argument should be kept or dropped.
-- We drop all arguments that have quantity 0 (= run-time erased).
-- We also drop hidden non-erased arguments (which should all be of
-- type Level or Set l).
keepArg :: (LensHiding a, LensModality a) => a -> Bool
keepArg x = usableModality x && visible x
-- Determine whether it is ok to erase arguments of this type,
-- even in the absence of an erasure annotation.
canErase :: Type -> C Bool
canErase a = do
TelV tel b <- telView a
addContext tel $ orM
[ isLevelType b
, isJust <$> isSizeType b
, ((isJust . isSort) <$> reduce (unEl b))
]
-- Exploits the fact that the name of the record type and the name of the record module are the
-- same, including the unique name ids.
isClassFunction :: QName -> C Bool
isClassFunction = isClassModule . qnameModule
isClassModule :: ModuleName -> C Bool
isClassModule m
| null $ mnameToList m = return False
| otherwise = do
minRec <- asks minRecordName
getConstInfo' (mnameToQName m) >>= \case
_ | Just m == minRec -> return True
Right Defn{defName = r, theDef = Record{}} ->
-- It would be nicer if we remembered this from when we looked at the record the first time.
processPragma r <&> \case
ClassPragma _ -> True
ExistingClassPragma -> True
_ -> False
_ -> return False
-- Drops the last (record) module for typeclass methods
dropClassModule :: ModuleName -> C ModuleName
dropClassModule m@(MName ns) = isClassModule m >>= \case
True -> dropClassModule $ MName $ init ns
False -> return m
moduleParametersToDrop :: ModuleName -> C Telescope
moduleParametersToDrop mod = do
reportSDoc "agda2hs.moduleParameters" 25 $ text "Getting telescope for" <+> prettyTCM mod
isDatatypeModule mod >>= \case
Just _ -> return EmptyTel
Nothing -> do
reportSDoc "agda2hs.moduleParameters" 25 $ text "Current context: " <+> (prettyTCM =<< getContext)
ctxArgs <- getContextArgs
reportSDoc "agda2hs.moduleParameters" 25 $ text "Context arguments: " <+> prettyTCM ctxArgs
sec <- lookupSection mod
reportSDoc "agda2hs.moduleParameters" 25 $ text "Module section: " <+> prettyTCM sec
return $ sec `apply` ctxArgs
isUnboxRecord :: QName -> C (Maybe Strictness)
isUnboxRecord q = do
getConstInfo q >>= \case
Defn{defName = r, theDef = Record{}} ->
processPragma r <&> \case
UnboxPragma s -> Just s
_ -> Nothing
_ -> return Nothing
isUnboxConstructor :: QName -> C (Maybe Strictness)
isUnboxConstructor q =
caseMaybeM (isRecordConstructor q) (return Nothing) $ isUnboxRecord . fst
isUnboxProjection :: QName -> C (Maybe Strictness)
isUnboxProjection q =
caseMaybeM (liftTCM $ getRecordOfField q) (return Nothing) isUnboxRecord
isTransparentFunction :: QName -> C Bool
isTransparentFunction q = do
getConstInfo q >>= \case
Defn{defName = r, theDef = Function{}} ->
processPragma r <&> \case
TransparentPragma -> True
_ -> False
_ -> return False
checkInstance :: Term -> C ()
checkInstance u | varOrDef u = liftTCM $ noConstraints $ do
reportSDoc "agda2hs.checkInstance" 5 $ text "checkInstance" <+> prettyTCM u
t <- infer u
reportSDoc "agda2hs.checkInstance" 15 $ text " inferred type:" <+> prettyTCM t
(m, v) <- newInstanceMeta "" t
reportSDoc "agda2hs.checkInstance" 15 $ text " instance meta:" <+> prettyTCM m
findInstance m Nothing
reportSDoc "agda2hs.checkInstance" 15 $ text " inferred instance:" <+> (prettyTCM =<< instantiate v)
reportSDoc "agda2hs.checkInstance" 65 $ text " inferred instance:" <+> (pure . P.pretty =<< instantiate v)
reportSDoc "agda2hs.checkInstance" 65 $ text " checking instance:" <+> (pure . P.pretty =<< instantiate u)
equalTerm t u v `catchError` \_ ->
genericDocError =<< text "illegal instance: " <+> prettyTCM u
where
varOrDef Var{} = True
varOrDef Def{} = True
varOrDef _ = False
-- We need to compile applications of `fromNat`, `fromNeg`, and
-- `fromString` where the constraint type is ⊤ or IsTrue .... Ideally
-- this constraint would be marked as erased but this would involve
-- changing Agda builtins.
checkInstance u@(Con c _ _)
| prettyShow (conName c) == "Agda.Builtin.Unit.tt" ||
prettyShow (conName c) == "Haskell.Prim.IsTrue.itsTrue" ||
prettyShow (conName c) == "Haskell.Prim.IsFalse.itsFalse" = return ()
checkInstance u = genericDocError =<< text "illegal instance: " <+> prettyTCM u
modifyLCase :: (Int -> Int) -> CompileState -> CompileState
modifyLCase f (CompileState {lcaseUsed = n}) = CompileState {lcaseUsed = f n}
incrementLCase, decrementLCase :: C ()
incrementLCase = modify $ modifyLCase (+ 1)
decrementLCase = modify $ modifyLCase (\n -> n - 1)
-- Always construct lambda cases with this function.
hsLCase :: [Hs.Alt ()] -> C (Hs.Exp ())
hsLCase = (incrementLCase >>) . return . Hs.LCase ()
ensureNoLocals :: String -> C ()
ensureNoLocals msg = unlessM (null <$> asks locals) $ genericError msg
withLocals :: LocalDecls -> C a -> C a
withLocals ls = local $ \e -> e { locals = ls }
checkValidVarName :: Hs.Name () -> C ()
checkValidVarName x = unless (validVarName x) $ genericDocError =<< do
text "Invalid name for Haskell variable: " <+> text (Hs.prettyPrint x)
checkValidTyVarName :: Hs.Name () -> C ()
checkValidTyVarName x = unless (validVarName x) $ genericDocError =<< do
text "Invalid name for Haskell type variable: " <+> text (Hs.prettyPrint x)
checkValidFunName :: Hs.Name () -> C ()
checkValidFunName x = unless (validVarName x) $ genericDocError =<< do
text "Invalid name for Haskell function: " <+> text (Hs.prettyPrint x)
checkValidTypeName :: Hs.Name () -> C ()
checkValidTypeName x = unless (validTypeName x) $ genericDocError =<< do
text "Invalid name for Haskell type: " <+> text (Hs.prettyPrint x)
checkValidConName :: Hs.Name () -> C ()
checkValidConName x = unless (validConName x) $ genericDocError =<< do
text "Invalid name for Haskell constructor: " <+> text (Hs.prettyPrint x)
tellImport :: Import -> C ()
tellImport imp = tell $ CompileOutput [imp] []
tellExtension :: Hs.KnownExtension -> C ()
tellExtension pr = tell $ CompileOutput [] [pr]
addPatBang :: Strictness -> Hs.Pat () -> C (Hs.Pat ())
addPatBang Strict p = tellExtension Hs.BangPatterns >>
return (Hs.PBangPat () p)
addPatBang Lazy p = return p
addTyBang :: Strictness -> Hs.Type () -> C (Hs.Type ())
addTyBang Strict ty = tellExtension Hs.BangPatterns >>
return (Hs.TyBang () (Hs.BangedTy ()) (Hs.NoUnpackPragma ()) ty)
addTyBang Lazy ty = return ty
checkSingleElement :: Hs.Name () -> [b] -> String -> C ()
checkSingleElement name fs s = unless (length fs == 1) $ genericDocError =<< do
text (s ++ ":") <+> text (Hs.prettyPrint name)
checkingVars :: C a -> C a
checkingVars = local $ \e -> e { checkVar = True }