agda2hs-1.0: src/Agda2Hs/Compile/Function.hs
{-# LANGUAGE OverloadedStrings #-}
module Agda2Hs.Compile.Function where
import Control.Monad ( (>=>), filterM, forM_ )
import Control.Monad.Reader ( asks )
import Data.Generics
import Data.List
import Data.Maybe ( fromMaybe, isJust )
import qualified Data.Text as Text
import qualified Language.Haskell.Exts.Syntax as Hs
import qualified Language.Haskell.Exts.Build as Hs
import Agda.Compiler.Backend
import Agda.Compiler.Common
import Agda.Syntax.Common
import Agda.Syntax.Internal
import Agda.Syntax.Literal
import Agda.TypeChecking.Pretty
import Agda.TypeChecking.Substitute
import Agda.TypeChecking.Telescope ( telView )
import Agda.TypeChecking.Sort ( ifIsSort )
import Agda.Utils.Functor ( (<&>) )
import Agda.Utils.Impossible ( __IMPOSSIBLE__ )
import Agda.Utils.Pretty ( prettyShow )
import Agda.Utils.Monad
import Agda2Hs.AgdaUtils
import Agda2Hs.Compile.Name ( compileQName )
import Agda2Hs.Compile.Term ( compileTerm, compileVar )
import Agda2Hs.Compile.Type ( compileTopLevelType )
import Agda2Hs.Compile.TypeDefinition ( compileTypeDef )
import Agda2Hs.Compile.Types
import Agda2Hs.Compile.Utils
import Agda2Hs.HsUtils
isSpecialPat :: QName -> Maybe (ConHead -> ConPatternInfo -> [NamedArg DeBruijnPattern] -> C (Hs.Pat ()))
isSpecialPat qn = case prettyShow qn of
"Haskell.Prim.Tuple._;_" -> Just tuplePat
"Agda.Builtin.Int.Int.pos" -> Just posIntPat
"Agda.Builtin.Int.Int.negsuc" -> Just negSucIntPat
s | s `elem` badConstructors -> Just $ \ _ _ _ -> genericDocError =<<
"constructor `" <> prettyTCM qn <> "` not supported in patterns"
_ -> Nothing
where
badConstructors =
[ "Agda.Builtin.Nat.Nat.zero"
, "Agda.Builtin.Nat.Nat.suc"
]
isUnboxCopattern :: DeBruijnPattern -> C Bool
isUnboxCopattern (ProjP _ q) = isJust <$> isUnboxProjection q
isUnboxCopattern _ = return False
tuplePat :: ConHead -> ConPatternInfo -> [NamedArg DeBruijnPattern] -> C (Hs.Pat ())
tuplePat cons i ps = do
let p = ConP cons i ps
err = sep [ "Tuple pattern"
, nest 2 $ prettyTCM p
, "does not have a known size." ]
xs <- makeListP' "Agda.Builtin.Unit.tt" "Haskell.Prim.Tuple._;_" err p
qs <- mapM compilePat xs
return $ Hs.PTuple () Hs.Boxed qs
-- Agda2Hs does not support natural number patterns directly (since
-- they don't exist in Haskell), however they occur as part of
-- patterns of type Integer, so we need to compile literal natural
-- number patterns.
compileLitNatPat :: DeBruijnPattern -> C Integer
compileLitNatPat = \case
ConP ch _ ps
| prettyShow (conName ch) == "Agda.Builtin.Nat.Nat.zero" -> return 0
| prettyShow (conName ch) == "Agda.Builtin.Nat.Nat.suc"
, [p] <- ps -> (1+) <$> compileLitNatPat (namedArg p)
p -> genericDocError =<< "not a literal natural number pattern:" <?> prettyTCM p
posIntPat :: ConHead -> ConPatternInfo -> [NamedArg DeBruijnPattern] -> C (Hs.Pat ())
posIntPat c i [p] = do
n <- compileLitNatPat (namedArg p)
return $ Hs.PLit () (Hs.Signless ()) (Hs.Int () n (show n))
posIntPat _ _ _ = __IMPOSSIBLE__
negSucIntPat :: ConHead -> ConPatternInfo -> [NamedArg DeBruijnPattern] -> C (Hs.Pat ())
negSucIntPat c i [p] = do
n <- (1+) <$> compileLitNatPat (namedArg p)
return $ Hs.PLit () (Hs.Negative ()) (Hs.Int () n (show (negate n)))
negSucIntPat _ _ _ = __IMPOSSIBLE__
-- The bool argument says whether we also want the type signature or just the body
compileFun, compileFun' :: Bool -> Definition -> C [Hs.Decl ()]
-- initialize locals when first stepping into a function
compileFun withSig def@Defn{..} = withFunctionLocals defName $ compileFun' withSig def
-- inherit existing (instantiated) locals
compileFun' withSig def@(Defn {..}) = do
reportSDoc "agda2hs.compile" 6 $ "compiling function: " <+> prettyTCM defName
let keepClause = maybe False keepArg . clauseType
withCurrentModule m $ setCurrentRange (nameBindingSite n) $ do
ifM (endsInSort defType) (ensureNoLocals err >> compileTypeDef x def) $ do
when withSig $ checkValidFunName x
compileTopLevelType withSig defType $ \ty -> do
-- Instantiate the clauses to the current module parameters
pars <- getContextArgs
reportSDoc "agda2hs.compile" 10 $ "applying clauses to parameters: " <+> prettyTCM pars
let clauses = filter keepClause funClauses `apply` pars
cs <- mapM (compileClause (qnameModule defName) x) clauses
return $ [Hs.TypeSig () [x] ty | withSig ] ++ [Hs.FunBind () cs]
where
Function{..} = theDef
m = qnameModule defName
n = qnameName defName
x = hsName $ prettyShow n
endsInSort t = do
TelV tel b <- telView t
addContext tel $ ifIsSort b (\_ -> return True) (return False)
err = "Not supported: type definition with `where` clauses"
compileClause :: ModuleName -> Hs.Name () -> Clause -> C (Hs.Match ())
compileClause curModule x c@Clause{..} = withClauseLocals curModule c $ do
reportSDoc "agda2hs.compile" 7 $ "compiling clause: " <+> prettyTCM c
addContext (KeepNames clauseTel) $ do
ps <- compilePats namedClausePats
ls <- asks locals
let
(children, ls') = partition
( not . isExtendedLambdaName
/\ (curModule `isFatherModuleOf`) . qnameModule )
ls
withLocals ls' $ do
body <- compileTerm $ fromMaybe __IMPOSSIBLE__ clauseBody
whereDecls <- mapM (getConstInfo >=> compileFun' True) children
let rhs = Hs.UnGuardedRhs () body
whereBinds | null whereDecls = Nothing
| otherwise = Just $ Hs.BDecls () (concat whereDecls)
match = case (x, ps) of
(Hs.Symbol{}, p : q : ps) -> Hs.InfixMatch () p x (q : ps) rhs whereBinds
_ -> Hs.Match () x ps rhs whereBinds
return match
noAsPatterns :: DeBruijnPattern -> C ()
noAsPatterns = \case
VarP i _ -> checkPatternInfo i
DotP i _ -> checkPatternInfo i
ConP _ cpi ps -> do
checkPatternInfo $ conPInfo cpi
forM_ ps $ noAsPatterns . namedArg
LitP i _ -> checkPatternInfo i
ProjP{} -> return ()
IApplyP i _ _ _ -> checkPatternInfo i
DefP i _ ps -> do
checkPatternInfo i
forM_ ps $ noAsPatterns . namedArg
where
checkPatternInfo i = unless (null $ patAsNames i) $
genericDocError =<< "not supported by agda2hs: as patterns"
compilePats :: NAPs -> C [Hs.Pat ()]
compilePats ps = mapM (compilePat . namedArg) =<< filterM keepPat ps
where
keepPat :: NamedArg DeBruijnPattern -> C Bool
keepPat p = do
keep <- return (keepArg p) `and2M` (not <$> isUnboxCopattern (namedArg p))
when keep $ noAsPatterns $ namedArg p
-- We do not allow forced (dot) patterns for non-erased arguments (see issue #142).
when (usableModality p && isForcedPat (namedArg p)) $
genericDocError =<< "not supported by agda2hs: forced (dot) patterns in non-erased positions"
return keep
isForcedPat :: DeBruijnPattern -> Bool
isForcedPat = \case
VarP{} -> False
DotP{} -> True
ConP c cpi ps -> conPLazy cpi
LitP{} -> False
ProjP{} -> False
IApplyP{} -> False
DefP{} -> False
compilePat :: DeBruijnPattern -> C (Hs.Pat ())
compilePat p@(VarP o x)
| PatOWild <- patOrigin o = return $ Hs.PWildCard ()
| otherwise = do
n <- hsName <$> compileVar (dbPatVarIndex x)
checkValidVarName n
return $ Hs.PVar () n
compilePat (ConP h i ps)
| Just semantics <- isSpecialPat (conName h) = setCurrentRange h $ semantics h i ps
compilePat (ConP h _ ps) = isUnboxConstructor (conName h) >>= \case
Just s -> compileErasedConP ps >>= addPatBang s
Nothing -> do
ps <- compilePats ps
c <- compileQName (conName h)
return $ pApp c ps
compilePat (LitP _ l) = compileLitPat l
compilePat (ProjP _ q) = do
reportSDoc "agda2hs.compile" 6 $ "compiling copattern: " <+> text (prettyShow q)
unlessM (asks copatternsEnabled) $
genericDocError =<< "not supported in Haskell: copatterns"
let x = hsName $ prettyShow q
return $ Hs.PVar () x
compilePat p = genericDocError =<< "bad pattern:" <?> prettyTCM p
compileErasedConP :: NAPs -> C (Hs.Pat ())
compileErasedConP ps = compilePats ps <&> \case
[p] -> p
_ -> __IMPOSSIBLE__
compileLitPat :: Literal -> C (Hs.Pat ())
compileLitPat = \case
LitChar c -> return $ Hs.charP c
l -> genericDocError =<< "bad literal pattern:" <?> prettyTCM l
-- Local (where) declarations ---------------------------------------------
-- | Before checking a function, grab all of its local declarations.
-- TODO: simplify this when Agda exposes where-provenance in 'Internal' syntax
withFunctionLocals :: QName -> C a -> C a
withFunctionLocals q k = do
ls <- takeWhile (isAnonymousModuleName . qnameModule)
. dropWhile (<= q)
. map fst
. sortDefs <$> liftTCM curDefs
withLocals ls k
-- | Retain only those local declarations that belong to current clause's module.
zoomLocals :: ModuleName -> LocalDecls -> LocalDecls
zoomLocals mname = filter ((mname `isLeParentModuleOf`) . qnameModule)
-- | Before checking a clause, grab all of its local declarationaas.
-- TODO: simplify this when Agda exposes where-provenance in 'Internal' syntax
withClauseLocals :: ModuleName -> Clause -> C a -> C a
withClauseLocals curModule c@Clause{..} k = do
ls <- asks locals
let
uses = filter
( (curModule `isFatherModuleOf`) . qnameModule
\/ (`extLamUsedIn` c) )
(getLocalUses ls c)
nonExtLamUses = qnameModule <$> filter (not . isExtendedLambdaName) uses
whereModuleName
| null uses = Nothing
| otherwise = Just $ head (nonExtLamUses ++ [curModule])
ls' = case whereModuleName of
Nothing -> []
Just m -> zoomLocals m ls
withLocals ls' k
checkTransparentPragma :: Definition -> C ()
checkTransparentPragma def = compileFun False def >>= \case
[Hs.FunBind _ cls] ->
mapM_ checkTransparentClause cls
[Hs.TypeDecl _ hd b] ->
checkTransparentTypeDef hd b
_ -> __IMPOSSIBLE__
where
checkTransparentClause :: Hs.Match () -> C ()
checkTransparentClause = \case
Hs.Match _ _ [p] (Hs.UnGuardedRhs _ e) _ | patToExp p == Just e -> return ()
_ -> errNotTransparent
checkTransparentTypeDef :: Hs.DeclHead () -> Hs.Type () -> C ()
checkTransparentTypeDef (Hs.DHApp _ _ (Hs.UnkindedVar _ x)) (Hs.TyVar _ y) | x == y = return ()
checkTransparentTypeDef _ _ = errNotTransparent
errNotTransparent = genericDocError =<<
"Cannot make function" <+> prettyTCM (defName def) <+> "transparent." <+>
"A transparent function must have exactly one non-erased argument and return it unchanged."