{-# LANGUAGE PatternGuards #-}
module Idris.Elab.Record(elabRecord) where
import Idris.AbsSyntax
import Idris.Core.Evaluate
import Idris.Core.TT
import Idris.Delaborate
import Idris.Docstrings
import Idris.Elab.Data
import Idris.Error
import Idris.Output (sendHighlighting)
import Control.Monad
import Data.List
import Data.Maybe
import qualified Data.Set as S
elabRecord :: ElabInfo
-> ElabWhat
-> (Docstring (Either Err PTerm))
-> SyntaxInfo
-> FC
-> DataOpts
-> Name
-> FC
-> [(Name, FC, Plicity, PTerm)]
-> [(Name, Docstring (Either Err PTerm))]
-> [(Maybe (Name, FC), Plicity, PTerm, Maybe (Docstring (Either Err PTerm)))]
-> Maybe (Name, FC)
-> (Docstring (Either Err PTerm))
-> SyntaxInfo
-> Idris ()
elabRecord info what doc rsyn fc opts tyn nfc params paramDocs fields cname cdoc csyn
= do logElab 1 $ "Building data declaration for " ++ show tyn
let tycon = generateTyConType params
logElab 1 $ "Type constructor " ++ showTmImpls tycon
dconName <- generateDConName (fmap fst cname)
let dconTy = generateDConType params fieldsWithNameAndDoc
logElab 1 $ "Data constructor: " ++ showTmImpls dconTy
logElab 1 $ foldr (++) "" $ intersperse "\n" (map show dconsArgDocs)
let datadecl = case what of
ETypes -> PLaterdecl tyn NoFC tycon
_ -> PDatadecl tyn NoFC tycon [(cdoc, dconsArgDocs, dconName, NoFC, dconTy, fc, [])]
elabData info rsyn doc paramDocs fc opts datadecl
let parameters = [(n,pt) | (n, _, _, pt) <- params]
let projections = [n | (n, _, _, _, _) <- fieldsWithName]
addRecord tyn (RI parameters dconName projections)
addIBC (IBCRecord tyn)
when (what /= ETypes) $ do
logElab 1 $ "fieldsWithName " ++ show fieldsWithName
logElab 1 $ "fieldsWIthNameAndDoc " ++ show fieldsWithNameAndDoc
elabRecordFunctions info rsyn fc tyn paramsAndDoc fieldsWithNameAndDoc dconName target
sendHighlighting $ S.fromList $
[(FC' nfc, AnnName tyn Nothing Nothing Nothing)] ++
maybe [] (\(_, cnfc) -> [(FC' cnfc, AnnName dconName Nothing Nothing Nothing)]) cname ++
[(FC' ffc, AnnBoundName fn False) | (fn, ffc, _, _, _) <- fieldsWithName]
where
generateTyConType :: [(Name, FC, Plicity, PTerm)] -> PTerm
generateTyConType ((n, nfc, p, t) : rest) = PPi p (nsroot n) nfc t (generateTyConType rest)
generateTyConType [] = (PType fc)
generateDConName :: Maybe Name -> Idris Name
generateDConName (Just n) = return $ expandNS csyn n
generateDConName Nothing = uniqueName (expandNS csyn $ sMN 0 ("Mk" ++ (show (nsroot tyn))))
where
uniqueName :: Name -> Idris Name
uniqueName n = do i <- getIState
case lookupTyNameExact n (tt_ctxt i) of
Just _ -> uniqueName (nextName n)
Nothing -> return n
generateDConType :: [(Name, FC, Plicity, PTerm)] -> [(Name, FC, Plicity, PTerm, a)] -> PTerm
generateDConType ((n, nfc, _, t) : ps) as = PPi impl (nsroot n) NoFC t (generateDConType ps as)
generateDConType [] ((n, _, p, t, _) : as) = PPi p (nsroot n) NoFC t (generateDConType [] as)
generateDConType [] [] = target
target :: PTerm
target = PApp fc (PRef fc [] tyn) $ map (uncurry asPRefArg) [(p, n) | (n, _, p, _) <- params]
paramsAndDoc :: [(Name, FC, Plicity, PTerm, Docstring (Either Err PTerm))]
paramsAndDoc = pad params paramDocs
where
pad :: [(Name, FC, Plicity, PTerm)] -> [(Name, Docstring (Either Err PTerm))] -> [(Name, FC, Plicity, PTerm, Docstring (Either Err PTerm))]
pad ((n, fc, p, t) : rest) docs
= let d = case lookup n docs of
Just d' -> d
Nothing -> emptyDocstring
in (n, fc, p, t, d) : (pad rest docs)
pad _ _ = []
dconsArgDocs :: [(Name, Docstring (Either Err PTerm))]
dconsArgDocs = paramDocs ++ (dcad fieldsWithName)
where
dcad :: [(Name, FC, Plicity, PTerm, Maybe (Docstring (Either Err PTerm)))] -> [(Name, Docstring (Either Err PTerm))]
dcad ((n, _, _, _, (Just d)) : rest) = ((nsroot n), d) : (dcad rest)
dcad (_ : rest) = dcad rest
dcad [] = []
fieldsWithName :: [(Name, FC, Plicity, PTerm, Maybe (Docstring (Either Err PTerm)))]
fieldsWithName = fwn [] fields
where
fwn :: [Name] -> [(Maybe (Name, FC), Plicity, PTerm, Maybe (Docstring (Either Err PTerm)))] -> [(Name, FC, Plicity, PTerm, Maybe (Docstring (Either Err PTerm)))]
fwn ns ((n, p, t, d) : rest)
= let nn = case n of
Just n' -> n'
Nothing -> newName ns baseName
withNS = expandNS rsyn (fst nn)
in (withNS, snd nn, p, t, d) : (fwn (fst nn : ns) rest)
fwn _ _ = []
baseName = (sUN "__pi_arg", NoFC)
newName :: [Name] -> (Name, FC) -> (Name, FC)
newName ns (n, nfc)
| n `elem` ns = newName ns (nextName n, nfc)
| otherwise = (n, nfc)
fieldsWithNameAndDoc :: [(Name, FC, Plicity, PTerm, Docstring (Either Err PTerm))]
fieldsWithNameAndDoc = fwnad fieldsWithName
where
fwnad :: [(Name, FC, Plicity, PTerm, Maybe (Docstring (Either Err PTerm)))] -> [(Name, FC, Plicity, PTerm, Docstring (Either Err PTerm))]
fwnad ((n, nfc, p, t, d) : rest)
= let doc = fromMaybe emptyDocstring d
in (n, nfc, p, t, doc) : (fwnad rest)
fwnad [] = []
elabRecordFunctions :: ElabInfo
-> SyntaxInfo
-> FC
-> Name
-> [(Name, FC, Plicity, PTerm, Docstring (Either Err PTerm))]
-> [(Name, FC, Plicity, PTerm, Docstring (Either Err PTerm))]
-> Name
-> PTerm
-> Idris ()
elabRecordFunctions info rsyn fc tyn params fields dconName target
= do logElab 1 $ "Elaborating helper functions for record " ++ show tyn
logElab 3 $ "Fields: " ++ show fieldNames
logElab 3 $ "Params: " ++ show paramNames
i <- getIState
ttConsTy <-
case lookupTyExact dconName (tt_ctxt i) of
Just as -> return as
Nothing -> tclift $ tfail $ At fc (Elaborating "record " tyn Nothing (InternalMsg "It seems like the constructor for this record has disappeared. :( \n This is a bug. Please report."))
let constructorArgs = getArgTys ttConsTy
logElab 3 $ "Cons args: " ++ show constructorArgs
logElab 3 $ "Free fields: " ++ show (filter (not . isFieldOrParam') constructorArgs)
let freeFieldsForElab = map (freeField i) (filter (not . isFieldOrParam') constructorArgs)
let paramsForElab = [((nsroot n), (paramName n), impl, t, d) | (n, _, _, t, d) <- params]
let userFieldsForElab = [((nsroot n), n, p, t, d) | (n, nfc, p, t, d) <- fields]
let projectors = [(n, n', p, t, d, i) | ((n, n', p, t, d), i) <- zip (freeFieldsForElab ++ paramsForElab ++ userFieldsForElab) [0..]]
elabProj dconName paramNames projectors
logElab 3 $ "Dependencies: " ++ show fieldDependencies
logElab 3 $ "Depended on: " ++ show dependedOn
let updaters = [(n, n', p, t, d, i) | ((n, n', p, t, d), i) <- zip (paramsForElab ++ userFieldsForElab) [0..]]
elabUp dconName paramNames updaters
where
placeholderArg :: Plicity -> Name -> PArg
placeholderArg p n = asArg p (nsroot n) Placeholder
fieldNames :: [Name]
fieldNames = [nsroot n | (n, _, _, _, _) <- fields]
paramNames :: [Name]
paramNames = [nsroot n | (n, _, _, _, _) <- params]
isFieldOrParam :: Name -> Bool
isFieldOrParam n = n `elem` (fieldNames ++ paramNames)
isFieldOrParam' :: (Name, a) -> Bool
isFieldOrParam' = isFieldOrParam . fst
freeField :: IState -> (Name, TT Name) -> (Name, Name, Plicity, PTerm, Docstring (Either Err PTerm))
freeField i arg = let nameInCons = fst arg
nameFree = expandNS rsyn (freeName $ fst arg)
plicity = impl
fieldType = delab i (snd arg)
doc = emptyDocstring
in (nameInCons, nameFree, plicity, fieldType, doc)
freeName :: Name -> Name
freeName (UN n) = sUN ("free_" ++ str n)
freeName (MN i n) = sMN i ("free_" ++ str n)
freeName (NS n s) = NS (freeName n) s
freeName n = n
paramName :: Name -> Name
paramName (UN n) = sUN ("param_" ++ str n)
paramName (MN i n) = sMN i ("param_" ++ str n)
paramName (NS n s) = NS (paramName n) s
paramName n = n
elabProj :: Name -> [Name] -> [(Name, Name, Plicity, PTerm, Docstring (Either Err PTerm), Int)] -> Idris ()
elabProj cn paramnames fs
= let phArgs = map (uncurry placeholderArg) [(p, n) | (n, _, p, _, _, _) <- fs]
elab = \(n, n', p, t, doc, i) ->
do let t' = projectInType [(m, m') | (m, m', _, _, _, _) <- fs
, not (m `elem` paramNames)] t
elabProjection info n paramnames n' p t' doc rsyn fc target cn phArgs fieldNames i
in mapM_ elab fs
elabUp :: Name -> [Name] -> [(Name, Name, Plicity, PTerm, Docstring (Either Err PTerm), Int)] -> Idris ()
elabUp cn paramnames fs
= let args = map (uncurry asPRefArg) [(p, n) | (n, _, p, _, _, _) <- fs]
elab = \(n, n', p, t, doc, i) -> elabUpdate info n paramnames n' p t doc rsyn fc target cn args fieldNames i (optionalSetter n)
in mapM_ elab fs
optionalSetter :: Name -> Bool
optionalSetter n = n `elem` dependedOn
fieldDependencies :: [(Name, [Name])]
fieldDependencies = map (uncurry fieldDep) [(n, t) | (n, _, _, t, _) <- fields ++ params]
where
fieldDep :: Name -> PTerm -> (Name, [Name])
fieldDep n t = ((nsroot n), paramNames ++ fieldNames `intersect` allNamesIn t)
dependedOn :: [Name]
dependedOn = concat ((catMaybes (map (\x -> lookup x fieldDependencies) fieldNames)))
elabProjection :: ElabInfo
-> Name
-> [Name]
-> Name
-> Plicity
-> PTerm
-> (Docstring (Either Err PTerm))
-> SyntaxInfo
-> FC -> PTerm
-> Name
-> [PArg]
-> [Name]
-> Int
-> Idris ()
elabProjection info cname paramnames pname plicity projTy pdoc psyn fc targetTy cn phArgs fnames index
= do logElab 1 $ "Generating Projection for " ++ show pname
let ty = generateTy
logElab 1 $ "Type of " ++ show pname ++ ": " ++ show ty
let lhs = generateLhs
logElab 1 $ "LHS of " ++ show pname ++ ": " ++ showTmImpls lhs
let rhs = generateRhs
logElab 1 $ "RHS of " ++ show pname ++ ": " ++ showTmImpls rhs
rec_elabDecl info EAll info ty
let clause = PClause fc pname lhs [] rhs []
rec_elabDecl info EAll info $ PClauses fc [] pname [clause]
where
generateTy :: PDecl
generateTy = PTy pdoc [] psyn fc [] pname NoFC $
bindParams paramnames $
PPi expl recName NoFC targetTy projTy
bindParams [] t = t
bindParams (n : ns) ty = PPi impl n NoFC Placeholder (bindParams ns ty)
generateLhs :: PTerm
generateLhs = let args = lhsArgs index phArgs
in PApp fc (PRef fc [] pname) [pexp (PApp fc (PRef fc [] cn) args)]
where
lhsArgs :: Int -> [PArg] -> [PArg]
lhsArgs 0 (_ : rest) = (asArg plicity (nsroot cname) (PRef fc [] pname_in)) : rest
lhsArgs i (x : rest) = x : (lhsArgs (i-1) rest)
lhsArgs _ [] = []
pname_in :: Name
pname_in = rootname
rootname :: Name
rootname = nsroot cname
generateRhs :: PTerm
generateRhs = PRef fc [] pname_in
elabUpdate :: ElabInfo
-> Name
-> [Name]
-> Name
-> Plicity
-> PTerm
-> (Docstring (Either Err PTerm))
-> SyntaxInfo
-> FC -> PTerm
-> Name
-> [PArg]
-> [Name]
-> Int
-> Bool
-> Idris ()
elabUpdate info cname paramnames pname plicity pty pdoc psyn fc sty cn args fnames i optional
= do logElab 1 $ "Generating Update for " ++ show pname
let ty = generateTy
logElab 1 $ "Type of " ++ show set_pname ++ ": " ++ show ty
let lhs = generateLhs
logElab 1 $ "LHS of " ++ show set_pname ++ ": " ++ showTmImpls lhs
let rhs = generateRhs
logElab 1 $ "RHS of " ++ show set_pname ++ ": " ++ showTmImpls rhs
let clause = PClause fc set_pname lhs [] rhs []
idrisCatch (do rec_elabDecl info EAll info ty
rec_elabDecl info EAll info $ PClauses fc [] set_pname [clause])
(\err -> logElab 1 $ "Could not generate update function for " ++ show pname)
where
generateTy :: PDecl
generateTy = PTy pdoc [] psyn fc [] set_pname NoFC $
bindParams paramnames $
PPi expl (nsroot pname) NoFC pty $
PPi expl recName NoFC sty (substInput sty)
where substInput = substMatches [(cname, PRef fc [] (nsroot pname))]
bindParams [] t = t
bindParams (n : ns) ty = PPi impl n NoFC Placeholder (bindParams ns ty)
set_pname :: Name
set_pname = set_name pname
set_name :: Name -> Name
set_name (UN n) = sUN ("set_" ++ str n)
set_name (MN i n) = sMN i ("set_" ++ str n)
set_name (NS n s) = NS (set_name n) s
set_name n = n
generateLhs :: PTerm
generateLhs = PApp fc (PRef fc [] set_pname) [pexp $ PRef fc [] pname_in, pexp constructorPattern]
where
constructorPattern :: PTerm
constructorPattern = PApp fc (PRef fc [] cn) args
pname_in :: Name
pname_in = in_name rootname
rootname :: Name
rootname = nsroot pname
generateRhs :: PTerm
generateRhs = PApp fc (PRef fc [] cn) (newArgs i args)
where
newArgs :: Int -> [PArg] -> [PArg]
newArgs 0 (_ : rest) = (asArg plicity (nsroot cname) (PRef fc [] pname_in)) : rest
newArgs i (x : rest) = x : (newArgs (i-1) rest)
newArgs _ [] = []
in_name :: Name -> Name
in_name (UN n) = sMN 0 (str n ++ "_in")
in_name (MN i n) = sMN i (str n ++ "_in")
in_name (NS n s) = NS (in_name n) s
in_name n = n
asArg :: Plicity -> Name -> PTerm -> PArg
asArg (Imp os _ _ _ _ _) n t = PImp 0 False os n t
asArg (Exp os _ _ _) n t = PExp 0 os n t
asArg (Constraint os _ _) n t = PConstraint 0 os n t
asArg (TacImp os _ s _) n t = PTacImplicit 0 os n s t
recName :: Name
recName = sMN 0 "rec"
recRef = PRef emptyFC [] recName
projectInType :: [(Name, Name)] -> PTerm -> PTerm
projectInType xs = mapPT st
where
st :: PTerm -> PTerm
st (PRef fc hls n)
| Just pn <- lookup n xs = PApp fc (PRef fc hls pn) [pexp recRef]
st t = t
asPRefArg :: Plicity -> Name -> PArg
asPRefArg p n = asArg p (nsroot n) $ PRef emptyFC [] (nsroot n)