{-# LANGUAGE CPP #-} {-# LANGUAGE RankNTypes #-} {- | Base module for `AntiQuoter`s, defining some basic type-aliases and and combinators for antiquoting. To for examples in the documentation of this library the following data types defining the untyped lambda calculus syntax: @ data Expr = VarE Var | Lam Var Expr | App Expr Expr | AntiExpr String deriving (Typeable, Data) data Var = Var String | AntiVar String deriving (Typeable, Data) @ (note: the idea for using lambda calculus comes from the original paper on quasi-quoting ) A simple quasi-quoter without support for antiquoting can be defined by: @ lExp = QuasiQuoter { quoteExp = dataToExpQ (const Nothing) . parseExpr , quotePat = dataToPatQ (const Nothing) . parseExpr , quoteType = error \"No type quoter\" , quoteDec = error \"No declaration quoter\" } parseExpr :: String -> Expr parseExpr = undefined -- implementation omitted @ Now to add antiquoting it is needed to treat the AntiExpr and AntiVar constructors as special and translate them ourselves. This introduces an @`AntiQuoterPass` e p@, which is a specific translation rule from source syntax @e@ to template haskell type @p@. In the example this can be used to defined: @ antiExprE :: AntiQuoterPass Expr Exp antiExprE (AntiExpr s) = Just . varE $ mkName s antiExprE _ = Nothing antiVarE :: AntiQuoterPass Var Exp antiVarE (AntiVar s) = Just . varE $ mkName s antiVarE _ = Nothing antiExprP :: AntiQuoterPass Expr Pat antiExprP (AntiExpr s) = Just . varP $ mkName s antiExprP _ = Nothing antiVarP :: AntiQuoterPass Var Pat antiVarP (AntiVar s) = Just . varP $ mkName s antiVarP _ = Nothing @ Both rules should be used when antiquoting as an exception to the base case (using the default translation, @const Nothing@). Which can be done using @(`<>>`)@, creating an `AntiQuoter`. Where an `AntiQuoter` represents a combination of `AntiQuoterPass`es which can be used to antiquote multiple datatypes. In the example this would result in @ lExp = QuasiQuoter { quoteExp = dataToExpQ antiE . parseExpr , quotePat = dataToPatQ antiP . parseExpr , quoteType = error \"No type quoter\" , quoteDec = error \"No declaration quoter\" } where antiE :: AntiQuoter Exp antiE = antiExprE \<>> antiVarE \<>> const Nothing antiP :: AntiQuoter Pat antiP = antiExprP \<>> antiVarP \<>> const Nothing @ Two little improvements could be made, @const Nothing@ could be replaced by `noAntiQuoter` and the building of the `QuasiQuoter` could be simplified by using `mkQuasiQuoter`. -} module Language.Haskell.AntiQuoter.Base( -- * AntiQuoters AntiQuoterPass, AntiQuoter, AQResult, -- ** Using AntiQuoters mkQuasiQuoter, fromPass, noAntiQuoter, (<<>>), (<<>), (<>>), -- ** Convenience reexport -- | WARNING: when combining AntiQuoter(Pass)es using `extQ` only the -- last (rightmost) pass will be used for any source type. The `<<>` -- and `<>>` don't suffer from this problem. extQ, -- ** Internals WrappedAQResult(..), ) where import Control.Monad import Data.Generics import Language.Haskell.TH import Language.Haskell.TH.Quote infixl 1 <<>,<<>> infixr 2 <>> -- | A single antiquotation for a specific source type. Usually @e@ is a type -- from the parsed language and @q@ is the target type (usually `Pat` or -- `Exp`). A @Just result@ indicates that the input should be antiquoted into -- @result@ while @Nothing@ indicates that there is no special antiquotation. type AntiQuoterPass e q = e -> Maybe (Q q) -- Note, `AQResult` is not used as that would lead to too unclear code and -- documentation. -- | Result of an `AntiQuoterPass` (AntiQuoterPass e q = e -> AQResult q). -- This type-alias is mostly used for combinators which only provides the -- result of the antiquotation and the usecase (thus the pattern to match) -- should be filled in by the user. -- -- See `AntiQuoterPass` on what @Nothing@ and @Just@ mean. type AQResult q = Maybe (Q q) -- | Wrapper for `AQResult`, needed for the typechecker. newtype WrappedAQResult q = AQRW { unAQRW :: AQResult q } -- | An `AntiQuoter` is the combination of several `AntiQuoterPass`es, which -- could have different source types. In the example the -- @AntiQuoterPass Expr Exp@ and @AntiQuoterPass Var Exp@ were combined into -- a single @AntiQuoter Exp@, which antiquoted both @Expr@ and @Pat@. type AntiQuoter q = forall e. Typeable e => AntiQuoterPass e q -- | Create an `AntiQuoter` from an single pass. fromPass :: Typeable e => AntiQuoterPass e q -> AntiQuoter q fromPass aqp = mkQ Nothing aqp -- | An `AnitQuoter` that does no antiquoting by only return Nothing, -- -- > noAntiQuoter = const Nothing -- noAntiQuoter :: AntiQuoter q noAntiQuoter = const Nothing -- | Create an `AntiQuoter` by combining an `AntiQuoter` and an -- `AntiQuoterPass`. This is left biased, see (`<<>>`). (<<>) :: Typeable e => AntiQuoter q -> AntiQuoterPass e q -> AntiQuoter q aq <<> aqp = aq <<>> fromPass aqp -- | Create an `AntiQuoter` by combining an `AntiQuoterPass` and an -- `AntiQuoter`. This is left biased, see (`<<>>`). (<>>) :: Typeable e => AntiQuoterPass e q -> AntiQuoter q -> AntiQuoter q aqp <>> aq = fromPass aqp <<>> aq -- | Combines two `AntiQuoter`s with the same result. It is left biased, thus -- if the first antiquoter returns @Just result@ that is used, otherwise the -- second AntiQuoter is tried. -- Together with `noAntiQuoter` this forms a monoid, but as AntiQuoter is a -- type synonyme no instance is declared. (<<>>) :: AntiQuoter q -> AntiQuoter q -> AntiQuoter q aq1 <<>> aq2 = \e -> aq1 e `mplus` aq2 e -- | Create an QuasiQuoter for expressions and patterns from a parser and two -- antiquoters. The quasiquoter from the example could also have been -- constructed by using @mkQuasiQuoter (return . parse) antiE antiP @. mkQuasiQuoter :: Data a => (String -> Q a) -> AntiQuoter Exp -> AntiQuoter Pat -> QuasiQuoter mkQuasiQuoter parse aqExp aqPat = QuasiQuoter { quoteExp = parse >=> dataToExpQ aqExp , quotePat = parse >=> dataToPatQ aqPat -- To prevent warnings #if MIN_VERSION_template_haskell(2,5,0) , quoteType = error $ "Language.Haskell.Antiquoter: can't handle types" , quoteDec = error $ "Language.Haskell.Antiquoter: can't handle decls" #endif }