hobbits-1.1: Data/Binding/Hobbits/QQ.hs
{-# LANGUAGE TemplateHaskell #-}
-- |
-- Module : Data.Binding.Hobbits.QQ
-- Copyright : (c) 2011 Edwin Westbrook, Nicolas Frisby, and Paul Brauner
--
-- License : BSD3
--
-- Maintainer : emw4@rice.edu
-- Stability : experimental
-- Portability : GHC
--
-- Defines a quasi-quoter for writing patterns that match the bodies of 'Mb'
-- multi-bindings. Uses the haskell-src-exts parser. @[nuP| P ]@ defines a
-- pattern that will match a multi-binding whose body matches @P@. Any
-- variables matched by @P@ will remain inside the binding; thus, for example,
-- in the pattern @[nuP| x |]@, @x@ matches the entire multi-binding.
--
-- Examples:
--
-- > case (nu Left) of [nuP| Left x |] -> x == nu id
--
-- [clP| P |] does the same for the Cl type, and [clNuP| P |] works for
-- both simultaneously: Cl (Mb ctx a).
module Data.Binding.Hobbits.QQ (nuP, clP, clNuP) where
import qualified Data.Binding.Hobbits.InternalUtilities as IU
import Data.Binding.Hobbits.Internal (Mb(..), Cl(..))
import Data.Binding.Hobbits.PatternParser (parsePattern)
import Language.Haskell.TH.Syntax as TH
import Language.Haskell.TH.Ppr as TH
import Language.Haskell.TH.Quote
import qualified Data.Generics as SYB
import Control.Monad.Writer (runWriterT, tell)
import Data.Monoid (Any(..))
compose :: Exp -> Exp -> Exp
compose f g = VarE '(.) `AppE` f `AppE` g
patQQ n pat = QuasiQuoter (err "Exp") pat (err "Type") (err "Decs")
where err s = error $ "QQ `" ++ n ++ "' is for patterns, not " ++ s ++ "."
data WrapKit =
-- _add adds structure just before binding the name (i.e. on VarP)
-- _rm removes structure that was added for the @ patterns
-- _top removes structure at the top of the whole pattern
WrapKit {_add :: Exp, _rm :: Pat -> Pat, _top :: Bool -> Pat -> Pat}
outsideKit (WrapKit {_add = addO, _rm = rmO, _top = topO})
(WrapKit {_add = addI, _rm = rmI, _top = topI}) =
WrapKit {_add = addO `compose` addI,
_rm = rmO . rmI,
_top = \b -> topO b . topI b}
-- wrapVars changes the types of names bound in a pattern
wrapVars :: Monad m => WrapKit -> Pat -> m Pat
wrapVars (WrapKit {_add = add, _rm = rm, _top = top}) pat = do
(pat', Any usedAdd) <- runWriterT m
return $ top usedAdd pat'
where
m = IU.everywhereButM (SYB.mkQ False isExp) (SYB.mkM w) pat
where isExp :: Exp -> Bool
-- don't recur into the expression part of view patterns
isExp _ = True
-- this should be called if the 'add' function is ever used
hit x = tell (Any True) >> return x
-- wraps up bound names
w p@VarP{} = hit $ ViewP add p
-- wraps for the bound name, then immediately unwraps
-- for the rest of the pattern
w (AsP v p) = hit $ ViewP add $ AsP v $ rm p
-- requires the expression to be closed
w (ViewP (VarE n) p) = return $ ViewP (VarE 'unCl `AppE` VarE n) p
w (ViewP e _) = fail $ "view function must be a single name: `" ++ show (TH.ppr e) ++ "'"
w p = return p
parseHere :: String -> Q Pat
parseHere s = do
fn <- loc_filename `fmap` location
case parsePattern fn s of
Left e -> error $ "Parse error: `" ++ e ++
"'\n\n\t when parsing pattern: `" ++ s ++ "'."
Right p -> return p
same_ctx :: Mb ctx a -> Mb ctx b -> Mb ctx b
same_ctx _ x = x
nuKit mb ln = WrapKit {_add = add, _rm = rm, _top = top} where
add = (VarE 'same_ctx `AppE` VarE mb) `compose`
(ConE 'MkMb `AppE` VarE ln)
rm p = ConP 'MkMb [WildP, p]
top b p = if b then AsP mb $ ConP 'MkMb [VarP ln, p] else rm p
nuP = patQQ "nuP" $ \s -> do
mb <- newName "mb"
ln <- newName "bs"
parseHere s >>= wrapVars (nuKit mb ln)
clKit = WrapKit {_add = ConE 'Cl, _rm = rm, _top = const rm}
where rm p = ConP 'Cl [p]
clP = patQQ "clP" $ (>>= wrapVars clKit) . parseHere
clNuP = patQQ "clNuP" $ \s -> do
mb <- newName "mb"
ln <- newName "bs"
parseHere s >>= wrapVars (clKit `outsideKit` nuKit mb ln)