agda2hs-1.0: src/Agda2Hs/Compile/Term.hs
module Agda2Hs.Compile.Term where
import Control.Arrow ( (>>>), (&&&) )
import Control.Monad ( unless )
import Control.Monad.Reader
import Data.List ( isPrefixOf )
import Data.Maybe ( fromMaybe, isJust )
import qualified Data.Text as Text ( unpack )
import qualified Language.Haskell.Exts as Hs
import Agda.Syntax.Common
import Agda.Syntax.Literal
import Agda.Syntax.Internal
import Agda.TypeChecking.Monad
import Agda.TypeChecking.Pretty
import Agda.TypeChecking.Reduce ( instantiate )
import Agda.TypeChecking.Substitute ( Apply(applyE) )
import Agda.Utils.Lens
import Agda.Utils.Pretty ( prettyShow )
import qualified Agda.Utils.Pretty as P
import Agda.Utils.Impossible ( __IMPOSSIBLE__ )
import Agda.Utils.Monad
import Agda.Utils.Size
import Agda2Hs.AgdaUtils
import Agda2Hs.Compile.Name ( compileQName )
import Agda2Hs.Compile.Types
import Agda2Hs.Compile.Utils
import Agda2Hs.HsUtils
import {-# SOURCE #-} Agda2Hs.Compile.Function ( compileClause )
isSpecialTerm :: QName -> Maybe (QName -> Elims -> C (Hs.Exp ()))
isSpecialTerm q = case prettyShow q of
_ | isExtendedLambdaName q -> Just lambdaCase
"Haskell.Prim.if_then_else_" -> Just ifThenElse
"Haskell.Prim.Enum.Enum.enumFrom" -> Just mkEnumFrom
"Haskell.Prim.Enum.Enum.enumFromTo" -> Just mkEnumFromTo
"Haskell.Prim.Enum.Enum.enumFromThen" -> Just mkEnumFromThen
"Haskell.Prim.Enum.Enum.enumFromThenTo" -> Just mkEnumFromThenTo
"Haskell.Prim.case_of_" -> Just caseOf
"Haskell.Prim.Monad.Do.Monad._>>=_" -> Just bind
"Haskell.Prim.Monad.Do.Monad._>>_" -> Just sequ
"Agda.Builtin.FromNat.Number.fromNat" -> Just fromNat
"Agda.Builtin.FromNeg.Negative.fromNeg" -> Just fromNeg
"Agda.Builtin.FromString.IsString.fromString" -> Just fromString
_ -> Nothing
isSpecialCon :: QName -> Maybe (ConHead -> ConInfo -> Elims -> C (Hs.Exp ()))
isSpecialCon = prettyShow >>> \case
"Haskell.Prim.Tuple._;_" -> Just tupleTerm
_ -> Nothing
fromNat :: QName -> Elims -> C (Hs.Exp ())
fromNat _ es = compileElims es <&> \case
_ : n@Hs.Lit{} : es' -> n `eApp` es'
es' -> hsVar "fromIntegral" `eApp` drop 1 es'
fromNeg :: QName -> Elims -> C (Hs.Exp ())
fromNeg _ es = compileElims es <&> \case
_ : n@Hs.Lit{} : es' -> Hs.NegApp () n `eApp` es'
es' -> (hsVar "negate" `o` hsVar "fromIntegral") `eApp` drop 1 es'
where
f `o` g = Hs.InfixApp () f (Hs.QVarOp () $ hsUnqualName "_._") g
fromString :: QName -> Elims -> C (Hs.Exp ())
fromString _ es = compileElims es <&> \case
_ : s@Hs.Lit{} : es' -> s `eApp` es'
es' -> hsVar "fromString" `eApp` drop 1 es'
tupleTerm :: ConHead -> ConInfo -> Elims -> C (Hs.Exp ())
tupleTerm cons i es = do
let v = Con cons i es
err = sep [ text "Tuple value"
, nest 2 $ prettyTCM v
, text "does not have a known size." ]
xs <- makeList' "Agda.Builtin.Unit.tt" "Haskell.Prim.Tuple._;_" err v
ts <- mapM compileTerm xs
return $ Hs.Tuple () Hs.Boxed ts
ifThenElse :: QName -> Elims -> C (Hs.Exp ())
ifThenElse _ es = compileElims es >>= \case
-- fully applied
b : t : f : es' -> return $ Hs.If () b t f `eApp` es'
-- partially applied
_ -> genericError $ "if_then_else must be fully applied"
mkEnumFrom :: QName -> Elims -> C (Hs.Exp ())
mkEnumFrom q es = compileElims es >>= \case
_ : a : es' -> return $ Hs.EnumFrom () a `eApp` es'
es' -> return $ hsVar "enumFrom" `eApp` drop 1 es'
mkEnumFromTo :: QName -> Elims -> C (Hs.Exp ())
mkEnumFromTo q es = compileElims es >>= \case
_ : a : b : es' -> return $ Hs.EnumFromTo () a b `eApp` es'
es' -> return $ hsVar "enumFromTo" `eApp` drop 1 es'
mkEnumFromThen :: QName -> Elims -> C (Hs.Exp ())
mkEnumFromThen q es = compileElims es >>= \case
_ : a : a' : es' -> return $ Hs.EnumFromThen () a a' `eApp` es'
es' -> return $ hsVar "enumFromThen" `eApp` drop 1 es'
mkEnumFromThenTo :: QName -> Elims -> C (Hs.Exp ())
mkEnumFromThenTo q es = compileElims es >>= \case
_ : a : a' : b : es' -> return $ Hs.EnumFromThenTo () a a' b `eApp` es'
es' -> return $ hsVar "enumFromThenTo" `eApp` drop 1 es'
delay :: QName -> Elims -> C (Hs.Exp ())
delay _ = compileErasedApp
force :: QName -> Elims -> C (Hs.Exp ())
force _ = compileErasedApp
bind :: QName -> Elims -> C (Hs.Exp ())
bind q (e:es) = do
checkInstance $ unArg $ isApplyElim' __IMPOSSIBLE__ e
compileElims es >>= \case
[u, Hs.Lambda _ [p] v] -> return (bind' u p v)
[u, Hs.LCase () [Hs.Alt () p (Hs.UnGuardedRhs () v) Nothing]] ->
decrementLCase >> return (bind' u p v)
vs -> return $ hsVar "_>>=_" `eApp` vs
where
bind' :: Hs.Exp () -> Hs.Pat () -> Hs.Exp () -> Hs.Exp ()
bind' u p v =
let stmt1 = Hs.Generator () p u in
case v of
Hs.Do _ stmts -> Hs.Do () (stmt1 : stmts)
_ -> Hs.Do () [stmt1, Hs.Qualifier () v]
bind q [] = return $ hsVar "_>>=_"
sequ :: QName -> Elims -> C (Hs.Exp ())
sequ q (e:es) = do
checkInstance $ unArg $ isApplyElim' __IMPOSSIBLE__ e
compileElims es >>= \case
(u : v : vs) -> do
let stmt1 = Hs.Qualifier () u
case v of
Hs.Do _ stmts -> return $ Hs.Do () (stmt1 : stmts)
_ -> return $ Hs.Do () [stmt1, Hs.Qualifier () v]
vs -> return $ hsVar "_>>_" `eApp` vs
sequ q [] = return $ hsVar "_>>_"
caseOf :: QName -> Elims -> C (Hs.Exp ())
caseOf _ es = compileElims es >>= \case
-- applied to pattern lambda
e : Hs.LCase _ alts : es' -> do
decrementLCase
return $ eApp (Hs.Case () e alts) es'
-- applied to regular lambda
e : Hs.Lambda _ (p : ps) b : es' -> do
let lam [] = id
lam qs = Hs.Lambda () qs
return $ eApp (Hs.Case () e [Hs.Alt () p (Hs.UnGuardedRhs () $ lam ps b) Nothing]) es'
-- applied to non-lambda / partially applied
_ -> genericError $ "case_of_ must be fully applied to a lambda"
lambdaCase :: QName -> Elims -> C (Hs.Exp ())
lambdaCase q es = setCurrentRange (nameBindingSite $ qnameName q) $ do
Function{funClauses = cls, funExtLam = Just ExtLamInfo {extLamModule = mname}}
<- theDef <$> getConstInfo q
npars <- size <$> lookupSection mname
let (pars, rest) = splitAt npars es
cs = applyE cls pars
ls <- filter (`extLamUsedIn` cs) <$> asks locals
cs <- withLocals ls $ mapM (compileClause (qnameModule q) $ hsName "(lambdaCase)") cs
case cs of
-- If there is a single clause and all patterns got erased, we
-- simply return the body.
[Hs.Match _ _ [] (Hs.UnGuardedRhs _ rhs) _] -> return rhs
_ -> do
lcase <- hsLCase =<< mapM clauseToAlt cs -- Pattern lambdas cannot have where blocks
eApp lcase <$> compileElims rest
clauseToAlt :: Hs.Match () -> C (Hs.Alt ())
clauseToAlt (Hs.Match _ _ [p] rhs wh) = pure $ Hs.Alt () p rhs wh
clauseToAlt (Hs.Match _ _ ps _ _) = genericError $ "Pattern matching lambdas must take a single argument"
clauseToAlt Hs.InfixMatch{} = __IMPOSSIBLE__
compileLiteral :: Literal -> C (Hs.Exp ())
compileLiteral (LitNat n) = return $ Hs.intE n
compileLiteral (LitFloat d) = return $ Hs.Lit () $ Hs.Frac () (toRational d) (show d)
compileLiteral (LitWord64 w) = return $ Hs.Lit () $ Hs.PrimWord () (fromIntegral w) (show w)
compileLiteral (LitChar c) = return $ Hs.charE c
compileLiteral (LitString t) = return $ Hs.Lit () $ Hs.String () s s
where s = Text.unpack t
compileLiteral l = genericDocError =<< text "bad term:" <?> prettyTCM (Lit l)
compileVar :: Nat -> C String
compileVar x = do
(d, n) <- (fmap snd &&& fst . unDom) <$> lookupBV x
let cn = prettyShow $ nameConcrete n
let b | notVisible d = "hidden"
| hasQuantity0 d = "erased"
| otherwise = ""
whenM (asks checkVar) $ unless (null b) $ genericDocError =<<
text ("Cannot use " <> b <> " variable " <> cn)
return cn
compileTerm :: Term -> C (Hs.Exp ())
compileTerm v = do
reportSDoc "agda2hs.compile" 7 $ text "compiling term:" <+> prettyTCM v
reportSDoc "agda2hs.compile" 27 $ text "compiling term:" <+> pure (P.pretty v)
case unSpine1 v of
Var x es -> do
s <- compileVar x
hsVar s `app` es
-- v currently we assume all record projections are instance
-- args that need attention
Def f es
| Just semantics <- isSpecialTerm f -> semantics f es
| otherwise -> isClassFunction f >>= \case
True -> compileClassFunApp f es
False -> (isJust <$> isUnboxProjection f) `or2M` isTransparentFunction f >>= \case
True -> compileErasedApp es
False -> do
-- Drop module parameters (unless projection-like)
n <- (theDef <$> getConstInfo f) >>= \case
Function{ funProjection = Right{} } -> return 0
_ -> size <$> lookupSection (qnameModule f)
(`app` drop n es) . Hs.Var () =<< compileQName f
Con h i es
| Just semantics <- isSpecialCon (conName h) -> semantics h i es
Con h i es -> isUnboxConstructor (conName h) >>= \case
Just _ -> compileErasedApp es
Nothing -> (`app` es) . Hs.Con () =<< compileQName (conName h)
Lit l -> compileLiteral l
Lam v b | usableModality v, getOrigin v == UserWritten -> do
when (patternInTeleName `isPrefixOf` absName b) $ genericDocError =<< do
text "Record pattern translation not supported. Use a pattern matching lambda instead."
unless (visible v) $ genericDocError =<< do
text "Implicit lambda not supported: " <+> prettyTCM (absName b)
hsLambda (absName b) <$> underAbstr_ b compileTerm
Lam v b | usableModality v ->
-- System-inserted lambda, no need to preserve the name.
underAbstraction_ b $ \ body -> do
x <- showTCM (Var 0 [])
let hsx = hsVar x
body <- compileTerm body
return $ case body of
Hs.InfixApp _ a op b
| a == hsx -> Hs.RightSection () op b -- System-inserted visible lambdas can only come from sections
_ -> hsLambda x body -- so we know x is not free in b.
Lam v b ->
-- Drop erased lambdas (#65)
underAbstraction_ b $ \ body -> compileTerm body
t -> genericDocError =<< text "bad term:" <?> prettyTCM t
where
app :: Hs.Exp () -> Elims -> C (Hs.Exp ())
app hd es = eApp <$> pure hd <*> compileElims es
-- `compileErasedApp` compiles an application of an erased constructor
-- or projection.
compileErasedApp :: Elims -> C (Hs.Exp ())
compileErasedApp es = compileElims es >>= \case
[] -> return $ hsVar "id"
(v:vs) -> return $ v `eApp` vs
-- `compileClassFunApp` is used when we have a record projection and we want to
-- drop the first visible arg (the record)
compileClassFunApp :: QName -> Elims -> C (Hs.Exp ())
compileClassFunApp f es = do
hf <- compileQName f
case dropWhile notVisible (fromMaybe __IMPOSSIBLE__ $ allApplyElims es) of
[] -> __IMPOSSIBLE__
(x:xs) -> do
curMod <- currentModule
reportSDoc "agda2hs.compile" 15 $ nest 2 $ vcat
[ text "symbol module: " <+> prettyTCM (qnameModule f)
, text "current module: " <+> prettyTCM curMod
]
unless (curMod `isLeChildModuleOf` qnameModule f) $ checkInstance $ unArg x
args <- compileArgs xs
return $ Hs.Var () hf `eApp` args
compileElims :: Elims -> C [Hs.Exp ()]
compileElims es = compileArgs $ fromMaybe __IMPOSSIBLE__ $ allApplyElims es
compileArgs :: Args -> C [Hs.Exp ()]
compileArgs args = mapMaybeM compileArg args
compileArg :: Arg Term -> C (Maybe (Hs.Exp ()))
compileArg x = do
reportSDoc "agda2hs.compile" 8 $ text "compiling argument" <+> prettyTCM x
if | keepArg x -> Just <$> compileTerm (unArg x)
| isInstance x, usableModality x -> Nothing <$ checkInstance (unArg $ x)
| otherwise -> return Nothing