alms-0.5.0: src/Meta/THHelpers.hs
{-# LANGUAGE
CPP,
DeriveDataTypeable,
RankNTypes,
TemplateHaskell,
TypeSynonymInstances #-}
module Meta.THHelpers (
-- * Simplified TH quasiquote
th,
-- * For initializing quasiquoters
newQuasi,
-- * Generic expression/pattern AST construction
ToSyntax(..),
-- * Miscellany
buildContext, typeOfTyVarBndr, conName,
) where
import Lexer (lid, uid)
import Util
import Compat (newQuasi)
import Data.Generics (Typeable, Data, everything, mkQ)
import Language.Haskell.TH
import Language.Haskell.TH.Quote
import Text.ParserCombinators.Parsec
import Text.ParserCombinators.Parsec.Language (haskell)
import Text.ParserCombinators.Parsec.Token
-- | A very limited Haskell abstract syntax for describing both
-- patterns and expressions
data HsAst = HsApp HsAst HsAst
| HsWild
| HsVar String
| HsCon String
| HsList [HsAst]
| HsRec String [(String, HsAst)]
| HsOr HsAst HsAst
| HsAnti String String
deriving (Show, Typeable, Data)
-- | The quasiquoter for building TH expressions
th :: QuasiQuoter
th = (newQuasi "th") { quoteExp = parseHs >=> hsToExpQ }
-- | Don't allow HsOr to the left of HsApp:
hsApp :: HsAst -> HsAst -> HsAst
hsApp (HsOr hs11 hs12) hs2 = HsOr (hsApp hs11 hs2) (hsApp hs12 hs2)
hsApp hs1 hs2 = HsApp hs1 hs2
-- | Turn AST into a TH expression that constructs a TH expression or
-- pattern, depending on the context.
--
-- In particular, if we parameterize TH types with the type of
-- expression they construct, hsToExpQ has the types:
--
-- @
-- HsAst -> ExpQ (ExpQ ???)
-- HsAst -> ExpQ (PatQ ???)
-- @
hsToExpQ :: HsAst -> ExpQ
hsToExpQ hs0 = do
name <- newName "underscore"
expr <- loop (antiName name) hs0
if hasUnderscore hs0
then lam1E (varP name) (return expr)
else return expr
where
antiName def "_" = def
antiName _ name = mkName name
loop n hs = case unfoldApp hs of
(HsAnti v "th", []) -> varE (n v)
(HsAnti v "", args) -> [| varS $(varE (n v))
$(listE (map (loop n) args)) |]
(HsAnti v "con", args)
-> [| conS $(varE (n v))
$(listE (map (loop n) args)) |]
(HsVar str, args) -> [| varS $(litE (qstringL str))
$(listE (map (loop n) args)) |]
(HsCon str, args) -> [| conS $(litE (qstringL str))
$(listE (map (loop n) args)) |]
(HsWild, []) -> [| wildS |]
(HsList hss, []) -> [| listS $(listE (map (loop n) hss)) |]
(HsRec con fs, []) -> [| recS (toName $(litE (stringL con)))
$(listE
[ [| fieldS (toName $(litE (stringL lj)))
$(loop n hj) |]
| (lj, hj) <- fs ]) |]
(HsOr hs1 hs2, []) -> [| whichS $(loop n hs1) $(loop n hs2) |]
(HsAnti _ tag, _) -> fail $ "hs: unrecognized antiquote: " ++ tag
(op, _:_) -> fail $ "hs: cannot apply " ++ show op ++
" to arguments"
(HsApp _ _, []) -> fail $ "hs: impossible!"
-- | Qualify a string literal with
qstringL :: String -> Lit
qstringL s = stringL ("Syntax." ++ s)
-- | Does the given AST contain an antiquote named '_'? If so, we
-- create an implicit parameter and fill it in there.
hasUnderscore :: HsAst -> Bool
hasUnderscore = everything (||) $ mkQ False check where
check (HsAnti "_" _) = True
check _ = False
-- Allow us to use both Strings and Names where Names are expected
class Show a => ToName a where
toName :: a -> Name
nameIsWild :: a -> Bool
instance ToName Name where
toName = id
nameIsWild = (== "_") . show
instance ToName String where
toName = mkName
nameIsWild = (== "_")
-- Generic constructors for building both patterns and expressions
class Data b => ToSyntax b where
varS :: ToName a => a -> [Q b] -> Q b
conS :: ToName a => a -> [Q b] -> Q b
listS :: [Q b] -> Q b
recS :: Name -> [Q (Name, b)] -> Q b
fieldS :: Name -> Q b -> Q (Name, b)
-- | Return the first argument in expression context and the second
-- in pattern context
whichS :: Q b -> Q b -> Q b
whichS':: Q Exp -> Q Pat -> Q b
-- | A wild card expression, interpreted as @()@ in expression
-- (strange, but often right)
wildS :: Q b
-- | Lift data, generically
dataS :: Data a => (forall c. Data c => c -> Maybe (Q b)) -> a -> Q b
instance ToSyntax Exp where
varS = foldl appE . varE . toName
conS = foldl appE . conE . toName
listS = listE
recS = recConE
fieldS = fieldExp
whichS = const
whichS' = const
wildS = conE (mkName "()")
dataS = dataToExpQ
instance ToSyntax Pat where
varS n []
| nameIsWild n = wildP
| otherwise = varP (toName n)
varS n _ = fail $ "hs: pattern can't have variable head: " ++ show n
conS = conP . toName
listS = listP
recS = recP
fieldS = fieldPat
whichS = const id
whichS' = const id
wildS = wildP
dataS = dataToPatQ
-- ! Build a type class context from a list of type class names
-- and parameter positions, given a list of binders to use
-- as parameters.
buildContext :: [TyVarBndr] -> [(Name, [Int])] -> CxtQ
buildContext = mapM . each . map typeOfTyVarBndr
where
each tvs (n, ixs) = classP n [ tvs !! ix | ix <- ixs ]
-- Turn a type variable binder into a type
typeOfTyVarBndr :: TyVarBndr -> TypeQ
typeOfTyVarBndr (PlainTV tv) = varT tv
typeOfTyVarBndr (KindedTV tv k) = sigT (varT tv) k
-- The name of a data constructor
conName :: Con -> Name
conName (NormalC n _) = n
conName (RecC n _) = n
conName (InfixC _ n _) = n
conName (ForallC _ _ con) = conName con
-- Figure out the head and arguments of a curried application
unfoldApp :: HsAst -> (HsAst, [HsAst])
unfoldApp (HsApp hs1 hs2) = second (++[hs2]) (unfoldApp hs1)
unfoldApp hs = (hs, [])
-- Parse a string into a (very limited) Haskell AST that can be
-- interpreted as both expression and pattern
parseHs :: String -> Q HsAst
parseHs str0 = do
loc <- location
case parse (start loc) "" str0 of
Left e -> fail (show e)
Right ast -> return ast
where
start loc = do
pos <- getPosition
setPosition $
(flip setSourceName) (loc_filename loc) $
(flip setSourceLine) (fst (loc_start loc)) $
(flip setSourceColumn) (snd (loc_start loc)) $
pos
spaces
level0 <* eof
level0 = hsOr <$> level1
<*> optionMaybe (reservedOp haskell "|" *> level1)
level1 = foldl1 hsApp <$> many1 level2
level2 = choice
[
HsWild <$ underscore,
HsVar <$> lid,
hsUid <$> uid
<*> optionMaybe (braces haskell
(sepBy recfield (comma haskell))),
HsList <$> brackets haskell (sepBy level0 (comma haskell)),
angles haskell (HsAnti <$> lid_
<*> option "" (colon haskell *>
option "" lid)),
parens haskell level0
]
recfield = (,) <$> lid <*> (reservedOp haskell "=" *> level0)
hsUid str (Just rec) = HsRec str rec
hsUid str Nothing = HsCon str
hsOr hs1 Nothing = hs1
hsOr hs1 (Just hs2) = HsOr hs1 hs2
underscore = symbol haskell "_"
lid_ = lid <|> underscore