{-# LANGUAGE CPP #-} {-# LANGUAGE TemplateHaskell #-} {-# LANGUAGE RecordWildCards #-} module Template.HSML.Internal.TH #ifdef TESTING where #else ( -- * Quasi Quoters for Simplified HSML hsml , m , hsmlStringWith , hsmlString , hsmlFileWith , hsmlFile , shsmlStringWith , shsmlString , shsmlFileWith , shsmlFile ) where #endif -------------------------------------------------------------------------------- import qualified Language.Haskell.TH as TH import qualified Language.Haskell.TH.Quote as TH import qualified Language.Haskell.TH.Syntax as TH import qualified Language.Haskell.Meta.Syntax.Translate as HE -------------------------------------------------------------------------------- import qualified Text.Blaze as B import qualified Text.Blaze.Internal as B -------------------------------------------------------------------------------- import Control.Applicative import Control.Arrow import Control.Monad -------------------------------------------------------------------------------- import Data.String import Data.Monoid -------------------------------------------------------------------------------- import qualified Template.HSML.Internal.Types as I import qualified Template.HSML.Internal.Parser as I -------------------------------------------------------------------------------- -- | QuasiQuoter for Simplified HSML expressions with default options. See -- `Template.HSML.Internal.Types.defaulOptionsS` for details. -- -- Example: -- -- > example :: Blaze.Text.Markup -- > example = [hsml| -- >

Page Title

-- >

-- > Some interesting paragraph. -- >

-- > |] hsml :: TH.QuasiQuoter hsml = TH.QuasiQuoter { TH.quoteExp = shsmlString , TH.quoteDec = const $ fail "You can not use the HSML QuasiQuoter as a declaration." , TH.quotePat = const $ fail "You can not use the HSML QuasiQuoter as a pattern." , TH.quoteType = const $ fail "You can not use the HSML QuasiQuoter as a type." } {-# INLINE hsml #-} -- | The same as `Templahe.HSML.Internal.TH.hsml`. -- -- Example: -- -- > example :: Blaze.Text.Markup -- > example = [m| -- >

Page Title

-- >

-- > Some interesting paragraph. -- >

-- > |] m :: TH.QuasiQuoter m = hsml {-# INLINE m #-} -------------------------------------------------------------------------------- -- HSML -- These functions parse the given file or string as a HSML document. -- If possible, splicing these generates a record field type and its instance of -- `Template.HSML.Internal.Types.IsTemplate`. -- | Parses HSML document from file with the given options. Results in -- record type and its `Template.HSML.Internal.Types.IsTemplate` instance. -- -- Example: -- -- > $(hsmlFileWith (defaultOptions "MyTemplate") "my_template.hsml") hsmlFileWith :: I.Options -- ^ HSML options. -> FilePath -- ^ Name of input file. -> TH.Q [TH.Dec] -- ^ Resulting type declaration and type-class instance. hsmlFileWith opts path = TH.runIO (readFile path) >>= hsmlStringWith opts {-# INLINE hsmlFileWith #-} -- | Parses HSML document from file with default options. Results in -- record type and its `Template.HSML.Internal.Types.IsTemplate` instance. -- -- Example: -- -- > $(hsmlFile "MyTemplate" "my_template.hsml") hsmlFile :: String -- ^ Name of the record type (passed to `Template.HSML.Internal.Types.defaultOptions`). -> FilePath -- ^ Name of input file. -> TH.Q [TH.Dec] -- ^ Resulting type declaration and type-class instance. hsmlFile = hsmlFileWith . I.defaultOptions {-# INLINE hsmlFile #-} -- | Parses HSML document string with the given options. Results in -- record type and its `Template.HSML.Internal.Types.IsTemplate` instance. -- -- Example: -- -- > $(hsmlStringWith (defaultOptions "MyTemplate") "

Paragraph

") hsmlStringWith :: I.Options -- ^ HSML options. -> String -- ^ Input string. -> TH.Q [TH.Dec] -- ^ Resulting type declaration and type-class instance. hsmlStringWith opts str = case I.hsmlTemplate str of Right tpl -> makeDec opts tpl Left err -> fail err {-# INLINE hsmlStringWith #-} -- | Parses HSML document from string with default options. Results in -- record type and its `Template.HSML.Internal.Types.IsTemplate` instance. -- -- Example: -- -- > $(hsmlString "MyTemplate" "

Paragraph

") hsmlString :: String -- ^ Name of the record type (passed to `Template.HSML.Internal.Types.defaultOptions`). -> String -- ^ Input string. -> TH.Q [TH.Dec] -- ^ Resulting type declaration and type-class instance. hsmlString = hsmlStringWith . I.defaultOptions {-# INLINE hsmlString #-} -------------------------------------------------------------------------------- -- Simplified HSML (without arguments) -- These functions parse the given file or string as a Simplified HSML document. -- If possible, splicing these results in an expression of the type -- `Text.Blaze.Markup`. -- | Parses Simplified HSML document from file with the given options. Results in -- expression of type `Text.Blaze.Markup`. -- -- Example: -- -- > example :: Text.Blaze.Markup -- > example = $(shsmlFileWith defaultOptionsS "my_template.hsml") shsmlFileWith :: I.Options -- ^ HSML Options. -> FilePath -- ^ Name of input file. -> TH.ExpQ -- ^ Resulting expression. shsmlFileWith opts path = TH.runIO (readFile path) >>= shsmlStringWith opts {-# INLINE shsmlFileWith #-} -- | Parses Simplified HSML document from file with default options. Results in -- expression of type `Text.Blaze.Markup`. -- -- Example: -- -- > example :: Text.Blaze.Markup -- > example = $(shsmlFile "my_template.hsml") shsmlFile :: FilePath -- ^ Name of input file. -> TH.ExpQ -- ^ Resulting expression. shsmlFile = shsmlFileWith I.defaultOptionsS {-# INLINE shsmlFile #-} -- | Parses Simplified HSML document from string with the given options. Results in -- expression of type `Text.Blaze.Markup`. -- -- Example: -- -- > example :: Text.Blaze.Markup -- > example = $(shsmlStringWith defaultOptionsS "

Paragraph

") shsmlStringWith :: I.Options -- ^ HSML options -> String -- ^ Input string. -> TH.ExpQ -- ^ Resulting expression. shsmlStringWith opts str = case I.shsmlTemplate str of Right I.Template{..} -> makeExp opts templateDecs templateSections Left err -> fail err {-# INLINE shsmlStringWith #-} -- | Parses Simplified HSML document from string with default options. Results in -- expression of type `Text.Blaze.Markup`. -- -- Example: -- -- > example :: Text.Blaze.Markup -- > example = $(shsmlString "

Paragraph

") shsmlString :: String -- ^ Input string. -> TH.ExpQ -- ^ Resulting expression. shsmlString = shsmlStringWith I.defaultOptionsS {-# INLINE shsmlString #-} -------------------------------------------------------------------------------- -- | Generates a record type and its instance of `IsTemplate`. makeDec :: I.Options -> I.Template -> TH.Q [TH.Dec] makeDec opts@(I.Options _ _ tname fname) (I.Template args decs sections) = do (vars, fields) <- makeVarsAndFields tdata <- makeData vars fields tinst <- makeInstance vars return [tdata, tinst] where dataName :: TH.Name dataName = HE.toName tname -- | This function generates the type variable names and fields. makeVarsAndFields :: TH.Q ([TH.Name], [TH.VarStrictType]) makeVarsAndFields = (reverse *** reverse) <$> foldM go ([], []) args where go (vs, fs) (I.Arg an (Just at)) = return (vs, (HE.toName $ fname an, TH.NotStrict, HE.toType at) : fs) go (vs, fs) (I.Arg an Nothing) = do v <- TH.newName "a" return (v : vs, (HE.toName $ fname an, TH.NotStrict, TH.VarT v) : fs) -- | This function generates the instance of IsTemplate. makeInstance :: [TH.Name] -> TH.DecQ makeInstance vars = -- instance .IsTemplate ( ) where -- renderTemplate ({ }) = -- TH.instanceD (return []) (TH.conT ''I.IsTemplate `TH.appT` contyp) [TH.funD 'I.renderTemplate [clause]] where -- This is the data type applied to all the type variables. contyp = foldl (\acc n -> acc `TH.appT` TH.varT n) (TH.conT dataName) vars -- This is the clause for renderTemplate. clause = TH.clause [TH.recP dataName binds] (TH.normalB $ makeExp opts decs sections) [] -- This is the { = , ... } pattern. binds = map (\(I.Arg n _) -> return (HE.toName $ fname n, TH.VarP $ HE.toName n)) args -- | This function generates the record type. makeData :: [TH.Name] -> [TH.VarStrictType] -> TH.DecQ makeData vars fields = return $ -- data = { } TH.DataD [] dataName (map TH.PlainTV vars) [TH.RecC dataName fields] [] -- | Generates an expression of the type `Text.Blaze.Markup` makeExp :: I.Options -> [I.Dec] -> [I.Section] -> TH.ExpQ makeExp opts decs sections = TH.letE (map (return . HE.toDec) decs) $ sectionsToExp opts sections -------------------------------------------------------------------------------- sectionsToExp :: I.Options -> [I.Section] -> TH.ExpQ sectionsToExp opts sections = [e| mconcat $(TH.listE $ map (sectionToExp opts) sections) :: B.Markup |] sectionToExp :: I.Options -> I.Section -> TH.ExpQ sectionToExp opts@I.Options{..} section = case section of (I.ElementNode name atts sections decs) -> [e| applyAttributes $(TH.listE $ map (attributeToExp opts) atts) $ B.Parent (fromString name) (fromString $ "<" <> name) (fromString $ " name <> ">") $(makeExp opts decs sections) |] (I.ElementLeaf name atts) -> [e| applyAttributes $(TH.listE $ map (attributeToExp opts) atts) $ B.Leaf (fromString name) (fromString $ "<" <> name) (fromString ">") |] (I.Text str) -> [e| B.string str |] (I.TextRaw str) -> [e| B.preEscapedString str |] (I.Expression e) -> if optExpToMarkup then [e| B.toMarkup $(toExp e) |] else toExp e applyAttributes :: [B.Attribute] -> B.MarkupM a -> B.MarkupM a applyAttributes attributes markup = foldl (B.!) markup attributes attributeToExp :: I.Options -> I.Attribute -> TH.ExpQ attributeToExp I.Options{..} (I.Attribute aname avalue) = [e| B.attribute (fromString $(attributeNameToExp aname)) (fromString (" " <> $(attributeNameToExp aname) <> "=\"")) $(attributeValueToExp avalue) |] where attributeNameToExp (I.AttributeNameText str) = [e| str |] attributeNameToExp (I.AttributeNameExp e) = toExp e attributeValueToExp (I.AttributeValueText str) = [e| B.toValue str |] attributeValueToExp (I.AttributeValueExp e) = if optExpToValue then [e| B.toValue $(toExp e) |] else toExp e toExp :: HE.ToExp a => a -> TH.ExpQ toExp = return . HE.toExp {- toExp :: HE.Exp -> TH.ExpQ toExp (HE.Var n) = return $ TH.VarE (HE.toName n) toExp (HE.Con n) = return $ TH.ConE (HE.toName n) toExp (HE.Lit l) = return $ TH.LitE (HE.toLit l) toExp (HE.InfixApp e o f) = TH.uInfixE (toExp e) (return $ HE.toExp o) (toExp f) toExp (HE.LeftSection e o) = TH.infixE (Just $ toExp e) (return $ HE.toExp o) Nothing toExp (HE.RightSection o f) = TH.infixE Nothing (return $ HE.toExp o) (Just $ toExp f) toExp (HE.App e f) = TH.appE (toExp e) (toExp f) toExp (HE.NegApp e) = TH.appE (TH.varE 'negate) (toExp e) toExp (HE.Lambda _ ps e) = TH.lamE (fmap (return . HE.toPat) ps) (toExp e) toExp (HE.Let bs e) = TH.letE (map return $ HE.hsBindsToDecs bs) (toExp e) toExp (HE.If a b c) = TH.condE (toExp a) (toExp b) (toExp c) toExp (HE.Do ss) = TH.doE (map (return . HE.toStmt) ss) toExp (HE.Tuple xs) = TH.tupE (fmap toExp xs) toExp (HE.List xs) = TH.listE (fmap toExp xs) toExp (HE.Paren e) = TH.parensE (toExp e) toExp (HE.RecConstr n xs) = TH.recConE (HE.toName n) (map (return . HE.toFieldExp) xs) toExp (HE.RecUpdate e xs) = TH.recUpdE (toExp e) (map (return . HE.toFieldExp) xs) toExp (HE.EnumFrom e) = TH.arithSeqE $ TH.fromR (toExp e) toExp (HE.EnumFromTo e f) = TH.arithSeqE $ TH.fromToR (toExp e) (toExp f) toExp (HE.EnumFromThen e f) = TH.arithSeqE $ TH.fromThenR (toExp e) (toExp f) toExp (HE.EnumFromThenTo e f g) = TH.arithSeqE $ TH.fromThenToR (toExp e) (toExp f) (toExp g) toExp (HE.ExpTypeSig _ e t) = TH.sigE (toExp e) (return $ HE.toType t) toExp (HE.Case e alts) = TH.caseE (toExp e) (map (return . HE.toMatch) alts) toExp (HE.QuasiQuote n string) = TH.parensE ([e|TH.quoteExp|] `TH.appE` TH.varE (HE.toName n)) `TH.appE` TH.litE (TH.stringL string) toExp _ = error "Failed toExp" -}