{-# LANGUAGE CPP #-} {-# LANGUAGE NamedFieldPuns #-} {-# LANGUAGE RecordWildCards #-} ------------------------------------------------------------------------------- -- | -- Module : Text.XML.Twiml.Internal.TH -- Copyright : (C) 2018 Mark Andrus Roberts -- License : BSD-style (see the file LICENSE) -- Maintainer : Mark Andrus Roberts -- Stability : provisional -- -- The code for defining TwiML verbs is highly-repetitive and follows a pattern. -- This module defines a little TwiML definition format and Template Haskell -- function for generating this code. -- -- For example, data types 'Pause', 'PauseF', and 'PauseAttributes' can all be -- generated from the following definition: -- -- @ -- Pause -- attributes -- duration, Natural, length -- recursive -- toXMLForGADT -- toAttrsForAttributes -- @ -- -- You should never need to import this module; it is only used during -- compilation of the library. ------------------------------------------------------------------------------- module Text.XML.Twiml.Internal.TH ( TwimlSpec(..) , example , exampleSpec , runTwimlSpecParser , twimlSpecToData , twimlSpecStringToData , s ) where import Control.Monad import Data.Char import Data.Default import Data.Maybe import Language.Haskell.TH import Language.Haskell.TH.Quote import Language.Haskell.TH.Syntax import Text.Parsec example :: String example = unlines [ "Say", " required", " String", " attributes", " voice, Voice", " loop, Bool", " recursive" ] exampleSpec :: TwimlSpec exampleSpec = case runTwimlSpecParser example of Right exampleSpec' -> exampleSpec' Left parseError -> error $ show parseError data TwimlSpec = TwimlSpec { twimlName :: String , parameters :: [Parameters] , recursive :: Bool , toXMLForGADT :: Bool , toAttrsForAttributes :: Bool } deriving Show instance Default TwimlSpec where def = TwimlSpec def def False False False data Parameters = Required { getRequiredTypes :: [String] } | Attributes { getAttributes :: [Attribute] } deriving Show data Attribute = Attribute { attributeName :: String , attributeType :: String , overrideName :: Maybe String } deriving Show isRequired :: Parameters -> Bool isRequired (Required _) = True isRequired _ = False isAttributes :: Parameters -> Bool isAttributes (Attributes _) = True isAttributes _ = False getAllRequired :: [Parameters] -> [String] getAllRequired = concatMap getRequiredTypes . filter isRequired getAllAttributes :: [Parameters] -> [Attribute] getAllAttributes = concatMap getAttributes . filter isAttributes hasAttributes :: TwimlSpec -> Bool hasAttributes = not . null . getAllAttributes . parameters attributeToVarStrictType :: (String -> String) -> Attribute -> VarStrictType attributeToVarStrictType f Attribute{..} = ( mkName $ f attributeName #if MIN_VERSION_template_haskell(2,11,0) , Bang NoSourceUnpackedness SourceStrict #else , IsStrict #endif , AppT (ConT $ mkName "Maybe") (ConT $ mkName attributeType) ) parametersToVarStrictTypes :: (String -> String) -> [Parameters] -> [VarStrictType] parametersToVarStrictTypes f = map (attributeToVarStrictType f) . getAllAttributes -- rnfI :: Int -> Exp -- rnfI i = rnfNames . take i $ map (mkName . return) ['a'..'z'] -- rnfNames :: [Name] -> Exp -- rnfNames [] = TupE [] -- rnfNames [a] = rnfName a -- rnfNames (a:as) = AppE (AppE (VarE $ mkName "seq") (rnfName a)) (rnfNames as) -- rnfName :: Name -> Exp -- rnfName name = AppE (VarE $ mkName "rnf") (VarE name) specToGADTName :: TwimlSpec -> Name specToGADTName TwimlSpec{..} = mkName $ twimlName ++ "F" specToAttributesName :: TwimlSpec -> Name specToAttributesName TwimlSpec{..} = mkName $ twimlName ++ "Attributes" specToGADTArity :: TwimlSpec -> Int specToGADTArity spec@(TwimlSpec{..}) = length (getAllRequired parameters) + (if hasAttributes spec then 1 else 0) + (if recursive then 1 else 0) specToGADTNames :: TwimlSpec -> [Name] specToGADTNames spec@(TwimlSpec{..}) = take (specToGADTArity spec) $ map (mkName . return) ['a'..'z'] specToGADTAttributesName :: TwimlSpec -> Maybe Name specToGADTAttributesName spec@(TwimlSpec{..}) = go $ zip parameters $ specToGADTNames spec where go [] = Nothing go ((Required _, _):rest) = go rest go ((Attributes _, name):_) = Just name specToGADTChildName :: TwimlSpec -> Maybe Name specToGADTChildName spec@(TwimlSpec{..}) = go $ zip parameters $ specToGADTNames spec where go [] = Nothing go ((Required _, name):_) = Just name go ((Attributes _, _):rest) = go rest specToGADTPat :: TwimlSpec -> Pat specToGADTPat spec@(TwimlSpec{..}) = ConP (specToGADTName spec) varPs where varPs = map VarP $ specToGADTNames spec specToAttributesListE :: TwimlSpec -> Exp specToAttributesListE (TwimlSpec{..}) = ListE . map go $ getAllAttributes parameters where go (Attribute{..}) = let name = LitE . StringL $ fromMaybe attributeName overrideName in AppE (AppE (VarE $ mkName "makeAttr") name) (VarE . mkName $ makeAttr attributeName) attrPrefix = '_' : map toLower twimlName makeAttr (a:ttrName) = attrPrefix ++ toUpper a : ttrName makeAttr _ = error "Unsupported" specToToXML :: TwimlSpec -> Exp specToToXML spec@(TwimlSpec{..}) = UInfixE (AppE (AppE (AppE (VarE $ mkName "makeElement") (LitE $ StringL twimlName)) (AppE (VarE $ mkName "toSomeNode") child)) attributesE) (ConE $ mkName ":") next where child = maybe (TupE []) VarE $ specToGADTChildName spec attributesE = maybe (ListE []) (AppE (VarE $ mkName "toAttrs") . VarE) $ specToGADTAttributesName spec next = if recursive then AppE (VarE $ mkName "toXML") (VarE . last $ specToGADTNames spec) else ListE [] specToStrictTypes :: TwimlSpec -> [StrictType] #if MIN_VERSION_template_haskell(2,11,0) specToStrictTypes spec@(TwimlSpec{..}) = go parameters ++ [(Bang NoSourceUnpackedness NoSourceStrictness, VarT $ mkName "a") | recursive] where #else specToStrictTypes spec@(TwimlSpec{..}) = go parameters ++ [(NotStrict, VarT $ mkName "a") | recursive] where #endif go [] = [] go (Required as :bs) = map stringToStrictType as ++ go bs #if MIN_VERSION_template_haskell(2,11,0) go (Attributes _ :bs) = (Bang NoSourceUnpackedness NoSourceStrictness, ConT $ specToAttributesName spec) : go bs #else go (Attributes _ :bs) = (NotStrict, ConT $ specToAttributesName spec) : go bs #endif #if MIN_VERSION_template_haskell(2,11,0) stringToStrictType a = (Bang NoSourceUnpackedness NoSourceStrictness, ConT $ mkName a) #else stringToStrictType a = (NotStrict, ConT $ mkName a) #endif -- gadtToDefExp :: TwimlSpec -> [Parameters] -> Exp -- gadtToDefExp spec@(TwimlSpec{..}) = go (ConE $ specToGADTName spec) . foldr ((+) . count) (if recursive then 1 else 0) where -- go conE 0 = conE -- go conE n = go (AppE conE defE) (n-1) -- defE = VarE $ mkName "def" -- count (Required r) = length r -- count _ = 1 attributesToDefExp :: Exp -> [Parameters] -> Exp attributesToDefExp conE = go conE . length . getAllAttributes where go conE 0 = conE go conE n = go (AppE conE defE) (n-1) defE = VarE $ mkName "def" instance Default Attribute where def = Attribute def def def parseTwimlSpec :: Parsec String () TwimlSpec parseTwimlSpec = do twimlName <- parseTwimlName parameters <- parseParameters recursive <- option False $ try parseRecursive toXMLForGADT <- option False parseToXMLForGADT toAttrsForAttributes <- option False parseToAttrsForAttributes eof return $ TwimlSpec twimlName parameters recursive toXMLForGADT toAttrsForAttributes parseTwimlName :: Parsec String () String parseTwimlName = many1 letter <* newline parseParameters :: Parsec String () [Parameters] parseParameters = many (try parseRequiredSection <|> try parseAttributesSection) parseRequiredSection :: Parsec String () Parameters parseRequiredSection = do string " required"; newline Required <$> many (try parseRequired) parseRequired :: Parsec String () String parseRequired = string " " >> many1 (noneOf "\n") <* newline parseAttributesSection :: Parsec String () Parameters parseAttributesSection = do string " attributes"; newline Attributes <$> many (try parseAttribute) parseAttribute :: Parsec String () Attribute parseAttribute = do string " " abc <- many1 (noneOf ",\n") `sepBy` string ", " <* newline case abc of [a,b] -> return $ Attribute a b Nothing a:b:[c] -> return . Attribute a b $ Just c _ -> mzero parseRecursive :: Parsec String () Bool parseRecursive = const True <$> string " recursive" <* newline parseToXMLForGADT :: Parsec String () Bool parseToXMLForGADT = const True <$> string " toXMLForGADT" <* newline parseToAttrsForAttributes :: Parsec String () Bool parseToAttrsForAttributes = const True <$> string " toAttrsForAttributes" <* newline runTwimlSpecParser :: String -> Either ParseError TwimlSpec runTwimlSpecParser = runParser parseTwimlSpec () "" s :: QuasiQuoter s = QuasiQuoter { quoteExp = stringE . trim , quotePat = undefined , quoteType = undefined , quoteDec = undefined } trim :: String -> String trim = trimTail . dropWhile isSpace trimTail :: String -> String trimTail "" = "\n" trimTail s = take (lastNonBlank s) s ++ "\n" where lastNonBlank = (+1) . fst . foldl acc (0, 0) acc (l, n) c | isSpace c = (l, n + 1) | otherwise = (n, n + 1) twimlSpecStringToData :: String -> DecsQ twimlSpecStringToData str = case runTwimlSpecParser str of Right twimlSpec -> twimlSpecToData twimlSpec Left msg -> error $ show msg -- | Create an indexed GADT from a name. For example, given "Foo", this -- generates (roughly) -- -- @@ -- data Foo -- data FooF i a where -- FooF :: a -> FooF '[Foo] a -- @@ twimlSpecToData :: TwimlSpec -> DecsQ twimlSpecToData spec@(TwimlSpec{..}) = pure $ [ emptyDataDecl , gadt -- , deriveDataForGADT -- , instanceDefaultForGADT -- , deriveEqForGADT , deriveFunctorForGADT , instanceFunctor1ForGADT -- , instanceNFDataForGADT -- , deriveOrdForGADT -- , deriveReadForGADT , deriveShowForGADT -- , instanceToXMLForGADT , attributes , instanceDefaultForAttributes ] ++ [instanceToXMLForGADT | toXMLForGADT] ++ [instanceToAttrsForAttributes | toAttrsForAttributes] where conName = mkName twimlName -- | @data Foo@ #if MIN_VERSION_template_haskell(2,12,0) emptyDataDecl = DataD [] conName [] Nothing [] [DerivClause Nothing []] #else #if MIN_VERSION_template_haskell(2,11,0) emptyDataDecl = DataD [] conName [] Nothing [] [] #else emptyDataDecl = DataD [] conName [] [] [] #endif #endif -- | Type variables @i :: [*]@ and @a@ i' = mkName "i" a' = mkName "a" i = VarT i' a = VarT a' tyVarBndrs = [KindedTV i' $ AppT ListT StarT, PlainTV a'] -- | @Proxy@ proxy = ConT $ mkName "Proxy" -- | @Proxy i@ proxyI = AppT proxy i -- | @'[Foo]@ list = AppT (AppT PromotedConsT (ConT conName)) PromotedNilT -- | @Proxy '[Foo]@ proxyList = AppT proxy list -- | @Proxy i ~ Proxy '[Foo]@ -- -- Unfortunately, this is the only way I know of to constrain the kind of -- @i@. cxt' = [AppT (AppT EqualityT proxyI) proxyList] conNameF = mkName $ twimlName ++ "F" con = ForallC [] cxt' . NormalC conNameF $ specToStrictTypes spec -- | @data FooF i a where FooF :: a -> FooF '[Foo] a@ #if MIN_VERSION_template_haskell(2,12,0) gadt = DataD [] conNameF tyVarBndrs Nothing [con] [DerivClause Nothing []] #else #if MIN_VERSION_template_haskell(2,11,0) gadt = DataD [] conNameF tyVarBndrs Nothing [con] [] #else gadt = DataD [] conNameF tyVarBndrs [con] [] #endif #endif dataN = mkName "Data" -- dataC = ConT dataN defaultN = mkName "Default" defaultC = ConT defaultN -- enumN = mkName "Enum" -- enumC = ConT enumN eqN = mkName "Eq" -- eqC = ConT eqN functorN = mkName "Functor" functorC = ConT functorN functor1N = mkName "Functor1" functor1C = ConT functor1N genericN = mkName "Generic" -- genericC = ConT genericN nfdataN = mkName "NFData" -- nfdataC = ConT nfdataN ordN = mkName "Ord" -- ordC = ConT ordN readN = mkName "Read" -- readC = ConT readN showN = mkName "Show" showC = ConT showN toAttrsN = mkName "ToAttrs" toAttrsC = ConT toAttrsN toXMLN = mkName "ToXML" toXMLC = ConT toXMLN -- | @instance Default a => Default (FooF i a) where def = FooF def ...@ #if MIN_VERSION_template_haskell(2,11,0) -- instanceDefaultForGADT = InstanceD Nothing [AppT defaultC a] (AppT defaultC (AppT (AppT (ConT conNameF) list) a)) [ValD (VarP $ mkName "def") (NormalB $ gadtToDefExp spec parameters) []] #else -- instanceDefaultForGADT = InstanceD [AppT defaultC a] (AppT defaultC (AppT (AppT (ConT conNameF) list) a)) [ValD (VarP $ mkName "def") (NormalB $ gadtToDefExp spec parameters) []] #endif -- | @deriving instance Data a => Data (FooF i a)@ #if MIN_VERSION_template_haskell(2,12,0) -- deriveDataForGADT = StandaloneDerivD Nothing [AppT dataC a] $ AppT dataC (AppT (AppT (ConT conNameF) list) a) #else -- deriveDataForGADT = StandaloneDerivD [AppT dataC a] $ AppT dataC (AppT (AppT (ConT conNameF) list) a) #endif -- | @deriving instance Eq a => Eq (FooF i a)@ #if MIN_VERSION_template_haskell(2,12,0) -- deriveEqForGADT = StandaloneDerivD Nothing [AppT eqC a] $ AppT eqC (AppT (AppT (ConT conNameF) i) a) #else -- deriveEqForGADT = StandaloneDerivD [AppT eqC a] $ AppT eqC (AppT (AppT (ConT conNameF) i) a) #endif -- | @deriving instance Functor (FooF i)@ #if MIN_VERSION_template_haskell(2,12,0) deriveFunctorForGADT = StandaloneDerivD Nothing [] $ AppT functorC (AppT (ConT conNameF) i) #else deriveFunctorForGADT = StandaloneDerivD [] $ AppT functorC (AppT (ConT conNameF) i) #endif -- | @instance Functor1 FooF where fmap1 = fmap@ #if MIN_VERSION_template_haskell(2,11,0) instanceFunctor1ForGADT = InstanceD Nothing [] (AppT functor1C $ ConT conNameF) [ValD (VarP $ mkName "fmap1") (NormalB . VarE $ mkName "fmap") []] #else instanceFunctor1ForGADT = InstanceD [] (AppT functor1C $ ConT conNameF) [ValD (VarP $ mkName "fmap1") (NormalB . VarE $ mkName "fmap") []] #endif -- | @instance NFData a => NFData (FooF i a) where rnf (FooF a ...) = rnf a `seq` ...@ #if MIN_VERSION_template_haskell(2,11,0) -- instanceNFDataForGADT = InstanceD Nothing [AppT nfdataC a] (AppT nfdataC (AppT (AppT (ConT conNameF) list) a)) [FunD (mkName "rnf") [Clause [specToGADTPat spec] (NormalB . rnfI $ specToGADTArity spec) []]] #else -- instanceNFDataForGADT = InstanceD [AppT nfdataC a] (AppT nfdataC (AppT (AppT (ConT conNameF) list) a)) [FunD (mkName "rnf") [Clause [specToGADTPat spec] (NormalB . rnfI $ specToGADTArity spec) []]] #endif -- | @deriving instance Ord a => Ord (FooF i a)@ #if MIN_VERSION_template_haskell(2,12,0) -- deriveOrdForGADT = StandaloneDerivD Nothing [AppT ordC a] $ AppT ordC (AppT (AppT (ConT conNameF) i) a) #else -- deriveOrdForGADT = StandaloneDerivD [AppT ordC a] $ AppT ordC (AppT (AppT (ConT conNameF) i) a) #endif -- | @deriving instance Read a => Read (FooF i a)@ #if MIN_VERSION_template_haskell(2,12,0) -- deriveReadForGADT = StandaloneDerivD Nothing [AppT readC a] $ AppT readC (AppT (AppT (ConT conNameF) list) a) #else -- deriveReadForGADT = StandaloneDerivD [AppT readC a] $ AppT readC (AppT (AppT (ConT conNameF) list) a) #endif -- | @deriving instance Show a => Show (FooF i a)@ #if MIN_VERSION_template_haskell(2,12,0) deriveShowForGADT = StandaloneDerivD Nothing [AppT showC a] $ AppT showC (AppT (AppT (ConT conNameF) i) a) #else deriveShowForGADT = StandaloneDerivD [AppT showC a] $ AppT showC (AppT (AppT (ConT conNameF) i) a) #endif -- | @instance ToXML a => ToXML (FooF i a) where toXML (FooF a ...) = makeElement "Foo" a ...@ #if MIN_VERSION_template_haskell(2,11,0) instanceToXMLForGADT = InstanceD Nothing [AppT toXMLC a | recursive] (AppT toXMLC (AppT (AppT (ConT conNameF) i) a)) #else instanceToXMLForGADT = InstanceD [AppT toXMLC a | recursive] (AppT toXMLC (AppT (AppT (ConT conNameF) i) a)) #endif [FunD (mkName "toXML") [Clause [specToGADTPat spec] (NormalB $ specToToXML spec) []]] attrPrefix = '_' : map toLower twimlName makeAttr (a:ttrName) = attrPrefix ++ toUpper a : ttrName makeAttr _ = error "Unsupported" attributesName = specToAttributesName spec -- | @data FooAttributes = FooAttributes{..} deriving (Data, Eq, Ord, Read, Show)@ -- -- All record fields should be camelCased and prefixed with "_foo". #if MIN_VERSION_template_haskell(2,12,0) attributes = DataD [] attributesName [] Nothing [RecC attributesName (parametersToVarStrictTypes makeAttr parameters)] . pure . DerivClause Nothing $ ConT <$> [dataN, eqN, genericN, nfdataN, ordN, readN, showN] #else #if MIN_VERSION_template_haskell(2,11,0) attributes = DataD [] attributesName [] Nothing [RecC attributesName (parametersToVarStrictTypes makeAttr parameters)] $ ConT <$> [dataN, eqN, genericN, nfdataN, ordN, readN, showN] #else attributes = DataD [] attributesName [] [RecC attributesName (parametersToVarStrictTypes makeAttr parameters)] [dataN, eqN, genericN, nfdataN, ordN, readN, showN] #endif #endif -- | @instance Default FooAttributes where def = FooAttributes def ...@ #if MIN_VERSION_template_haskell(2,11,0) instanceDefaultForAttributes = InstanceD Nothing [] (AppT defaultC $ ConT attributesName) [ValD (VarP $ mkName "def") (NormalB $ attributesToDefExp (ConE attributesName) parameters) []] #else instanceDefaultForAttributes = InstanceD [] (AppT defaultC $ ConT attributesName) [ValD (VarP $ mkName "def") (NormalB $ attributesToDefExp (ConE attributesName) parameters) []] #endif #if MIN_VERSION_template_haskell(2,11,0) instanceToAttrsForAttributes = InstanceD Nothing [] (AppT toAttrsC $ ConT attributesName) [ValD (VarP $ mkName "toAttrs") (NormalB (AppE (AppE (VarE $ mkName "flip") (VarE $ mkName "makeAttrs")) (specToAttributesListE spec))) []] #else instanceToAttrsForAttributes = InstanceD [] (AppT toAttrsC $ ConT attributesName) [ValD (VarP $ mkName "toAttrs") (NormalB (AppE (AppE (VarE $ mkName "flip") (VarE $ mkName "makeAttrs")) (specToAttributesListE spec))) []] #endif