{-# LANGUAGE TemplateHaskell #-} {-# LANGUAGE QuasiQuotes #-} -- | -- Module : Expresso.TH.QQ -- Copyright : (c) Tim Williams 2017-2019 -- License : BSD3 -- -- Maintainer : info@timphilipwilliams.com -- Stability : experimental -- Portability : portable -- -- Quasi-quoters for defining Expresso types in Haskell. -- module Expresso.TH.QQ (expressoType, expressoTypeSyn) where import Control.Exception import Language.Haskell.TH (ExpQ, Loc(..), Q, location, runIO) import Language.Haskell.TH.Quote (QuasiQuoter(..), dataToExpQ) import qualified Text.Parsec as P import qualified Text.Parsec.Pos as P import Text.Parsec.String import Expresso.Parser -- | Expresso Quasi-Quoter for type declarations. expressoType :: QuasiQuoter expressoType = def { quoteExp = genTypeAnn } -- | Expresso Quasi-Quoter for type synonym declarations. expressoTypeSyn :: QuasiQuoter expressoTypeSyn = def { quoteExp = genTypeSynDecl } def :: QuasiQuoter def = QuasiQuoter { quoteExp = failure "expressions" , quotePat = failure "patterns" , quoteType = failure "types" , quoteDec = failure "declarations" } where failure kind = fail $ "This quasi-quoter does not support splicing " ++ kind genTypeAnn :: String -> ExpQ genTypeAnn str = do l <- location' c <- runIO $ parseIO (P.setPosition l *> topLevel pTypeAnn) str dataToExpQ (const Nothing) c genTypeSynDecl :: String -> ExpQ genTypeSynDecl str = do l <- location' c <- runIO $ parseIO (P.setPosition l *> topLevel pSynonymDecl) str dataToExpQ (const Nothing) c -- | find the current location in the Haskell source file and convert it to parsec @SourcePos@. location' :: Q P.SourcePos location' = aux <$> location where aux :: Loc -> P.SourcePos aux loc = uncurry (P.newPos (loc_filename loc)) (loc_start loc) parseIO :: Parser a -> String -> IO a parseIO p str = case P.parse p "" str of Left err -> throwIO (userError (show err)) Right a -> return a