haskhol-core-1.1.0: src/HaskHOL/Core/Ext.hs
{-# LANGUAGE ScopedTypeVariables #-}
{-|
Module: HaskHOL.Core.Ext
Copyright: (c) The University of Kansas 2013
LICENSE: BSD3
Maintainer: ecaustin@ittc.ku.edu
Stability: unstable
Portability: unknown
This module exports HaskHOL's non-trivial extensions to the underlying HOL
system, i.e. the compile time operations. These operations are split into
three categories:
* Methods related to the Protect and Serve Mechanism for sealing and unsealing
data against a provided theory context.
* Methods related to quasi-quoting of 'HOLTerm's.
* Methods related to compile time extension and caching of theory contexts.
-}
module HaskHOL.Core.Ext
( -- * Protected Data Methods
-- $Protect
module HaskHOL.Core.Ext.Protected
-- * Quasi-Quoter Methods
-- $QQ
, module HaskHOL.Core.Ext.QQ
-- * Theory Extension Methods
, templateProvers
, extendTheory
-- * Template Haskell Re-Exports
, module Language.Haskell.TH {-|
Re-exports 'Q', 'Dec', and 'Exp' for the purpose of writing type
signatures external to this module.
-}
, module Language.Haskell.TH.Quote {-|
Re-exports 'QuasiQuoter' for the purpose of writing type signatures
external to this module.
-}
) where
import HaskHOL.Core.Lib hiding (combine)
import HaskHOL.Core.State.Monad
import HaskHOL.Core.Ext.Protected
import HaskHOL.Core.Ext.QQ
import Data.Char (toUpper, toLower)
import Language.Haskell.TH (Q, Dec, Exp)
import Language.Haskell.TH.Quote (QuasiQuoter)
import Language.Haskell.TH.Syntax
import Prelude hiding (FilePath)
import Paths_haskhol_core
import Shelly
import System.FilePath (combine)
{-|
The 'templateTypes' splice automatically generates the types and type
families associated with a theory extension. It constructs the following:
* A new empty data declaration associated with the new theory.
* A type synonym for the application of the new data type.
* A closed type family for constraining computations to be used only with
contexts containing this checkpoint.
The first argument is the context to be extended and the second argument is
the name of the new theory checkpoint.
For example,
> templateTypes ctxtBase "Bool"
will produce the following declarations:
> data BoolThry
> instance CtxtName BoolThry where
> ctxtName _ = "BoolCtxt"
> type BoolType = ExtThry BoolThry BaseThry
>
> type family BoolContext a :: Bool where
> BoolContext BaseThry = False
> BoolContext (ExtThry a b) = (a == BoolThry) || (BoolContext b)
>
> type instance BoolThry == BoolThry = True
Regrettably, Template Haskell cannot currently handle splicing type
equalities. Thus, to provide a cleaner interface, it is recommended to
manually construct another type family to be used as the main 'Constraint':
> type family BoolCtxt a :: Constraint where
> BoolCtxt a = (BaseCtxt a, BoolContext a ~ True)
Note that this new 'Constraint' can also be used to enforce the linearity of
type theory contexts.
-}
templateTypes :: forall thry. CtxtName thry => TheoryPath thry -> String
-> Q [Dec]
templateTypes _ lbl =
-- upper case label for everything else
let upLbl = toUpper (head lbl) : tail lbl
-- type of old theory
oldThry = ConT . mkName $
let oldCtxt = ctxtName (undefined::thry)
oldType = take (length oldCtxt - 4) oldCtxt in
if oldType == "Base" then "BaseThry"
else oldType ++ "Type"
-- general use type variables
aName = mkName "a"
aVar = VarT aName
bVar = VarT $ mkName "b"
-- build data types
dataName = mkName $ upLbl ++ "Thry"
dataType = ConT dataName
-- splices: instance CtxtName _Thry where ...
instDec = InstanceD [] (AppT (ConT ''CtxtName) (ConT dataName))
[FunD 'ctxtName [Clause [WildP]
(NormalB (LitE (StringL $ upLbl ++ "Ctxt"))) []]]
dataDec = DataD [] dataName [] [] []
tyName = mkName $ upLbl ++ "Type"
newThry = ConT ''ExtThry `AppT` dataType `AppT` oldThry
-- splices: type _Type = ExtThry _Thry oldThry
tyDec = TySynD tyName [] newThry
-- build class and instances
clsName = mkName $ upLbl ++ "Context"
-- splices: type instance _Context BaseThry = False
famBase =TySynEqn [ConT ''BaseThry] $ PromotedT 'False
famCond = AppT (AppT (ConT ''(||))
(AppT (ConT clsName) bVar))
(AppT (AppT (ConT ''(==)) aVar) (ConT dataName))
-- splices: type instance _Context (ExtThry a b) =
-- (a == _Thry) || (_Context b)
famInd = TySynEqn [AppT (AppT (ConT ''ExtThry) aVar) bVar] famCond
-- splices: type family _Context a :: Bool
fam = ClosedTypeFamilyD clsName [PlainTV aName] (Just $ ConT ''Bool)
[famBase, famInd]
-- class wrapper
-- splices: type instance _Thry == _Thry = True
clsEq = TySynInstD ''(==) .
TySynEqn [ConT dataName, ConT dataName] $ PromotedT 'True in
return [ dataDec, tyDec, instDec -- types
, fam
, clsEq
]
{-|
The 'templateProvers' splice automatically generates the 'QuasiQuoter'
associated with a theory extension. The provided
name should be the theory context checkpoint built with the 'extendTheory'
splice.
For example,
> templateProvers 'ctxtBool
will produce the following declarations:
> bool :: QuasiQuoter
> bool = baseQuoter ctxtBool
-}
templateProvers :: Name -> Q [Dec]
templateProvers ctxName =
-- build QuasiQuoter
let upLbl = drop 4 $ nameBase ctxName
lowLbl = toLower (head upLbl) : tail upLbl
--tyName = mkName $ upLbl ++ "Type"
qqName = mkName lowLbl
-- splices: _ :: QuasiQuoter
qqTySig = SigD qqName $ ConT ''QuasiQuoter
-- splices: _ = baseQuoter ctxt_
qqDec = ValD (VarP qqName) (NormalB $
VarE 'baseQuoter `AppE` VarE ctxName) [] in
return [ qqTySig, qqDec ]
-- Documentation copied from sub-modules
{-$Protect
The basic goal behind the Protect and Serve mechanism is to recapture some of
the efficiency lost as a result of moving from an impure, interpretted host
language to a pure, compiled one. We do this by forcing the evaluation of
large computations, usually proofs, such that they are only run once. To
maintain soundness of our proof system, we must track what information
was used to force the computation and guarantee that information is present
in all cases where this new value is to be used. This is the purpose of the
@Protected@ class and the 'liftProtectedExp' and 'liftProtected' methods.
-}
{-$QQ
Quasi-quoting provides a way to parse 'HOLTerm's at compile time safely.
Just as with proofs, we seal these terms against the theory context used to
parse them with 'protect' and 'serve' to preserve soundness. See the
documentation for 'base' for a brief discussion on when quasi-quoting should
be used vs. 'toHTm'.
-}
{-|
The 'extendTheory' splice acts as a compile-time wrapper to 'runHOL', used for
the purpose of creating new theory contexts. It takes three arguments:
* The theory to be extended.
* The base name of the new theory to be created.
* The 'HOL' computation that will perform the extension.
For example:
> extendTheory ctxtBase "Bool" $ ...
Additionally, 'extendTheory' calls the 'templateTypes' splice to create the
type class wizardy associated with theory contexts and creates an
appropriately typed wrapper for 'mkTheoryPath', e.g.:
> ctxtBool :: TheoryPath BoolType
> ctxtBool = mkTheoryPath
-}
extendTheory :: CtxtName thry => TheoryPath thry -> String -> HOL Theory thry ()
-> Q [Dec]
extendTheory old new ld =
-- run the load function
do runIO $
do dir <- getDataDir
let new' = dir `combine` (new ++ "Ctxt")
shelly $
do cond <- test_d . fromText $ pack new'
when cond . echo . pack $
"Note: Using existing theory context for " ++ new
unless cond . liftIO $
runHOL (ld >> checkpointProofs) old new'
-- splices: ctxt_ :: TheoryPath _Type
-- ctxt_ = mkTheoryPath
let cname = mkName $ "ctxt" ++ new
ctype = ConT . mkName $ new ++ "Type"
tySig = SigD cname (ConT ''TheoryPath `AppT` ctype)
def = ValD (VarP cname) (NormalB (VarE 'mkTheoryPath)) []
tyDefs <- templateTypes old new
return $! tyDefs ++ [tySig, def]