{-# LANGUAGE ExistentialQuantification, TemplateHaskell, QuasiQuotes, OverloadedStrings, FlexibleInstances, UndecidableInstances, IncoherentInstances #-} -- | -- A simple 'QuasiQuoter' for 'Text' strings. Note that to use 'embed' you need to use the OverloadedStrings extension. module Text.QuasiText (embed, Chunk (..), getChunks) where import Language.Haskell.TH.Quote import Language.Haskell.TH.Syntax import Language.Haskell.TH import Language.Haskell.Meta (parseExp) import Data.Attoparsec.Text import Data.Text as T (Text, pack, unpack, append, empty, head, strip) instance Lift Text where lift t = litE (stringL (unpack t)) data Chunk = T Text | E Text | V Text deriving (Show, Eq) class Textish a where toText :: a -> Text instance Textish Text where toText = id instance Textish [Char] where toText = pack instance Show a => Textish a where toText = pack . show -- | A simple 'QuasiQuoter' to interpolate 'Text' into other pieces of 'Text'. -- Expressions can be embedded using $(...) or $..., $... will only work for one-word expressions (best suited for just -- variable substitution), but $(...) will work for anything.. embed :: QuasiQuoter embed = QuasiQuoter { quoteExp = \s -> let chunks = flip map (getChunks (pack s)) $ \c -> case c of -- literal text T t -> [| t |] -- haskell expression E t -> let Right e = parseExp (unpack t) in appE [| toText |] (return e) -- one-word expression V t | T.head t `elem` ['a'..'z'] -> appE [| toText |] (global (mkName (unpack t))) | otherwise -> let Right e = parseExp (unpack t) in appE [| toText |] (return e) in foldr (\l r -> appE (appE [| append |] l) r) [| empty |] chunks , quotePat = error "cannot use this as a pattern" , quoteDec = error "cannot use this as a declaration" , quoteType = error "cannot use this as a type" } -- | Create 'Chunk's without any TH. getChunks :: Text -> [Chunk] getChunks i = let Right m = parseOnly parser (strip i) in m where parser = go [] go s = do txt <- takeTill (== '$') evt <- choice [expression, var, fmap T takeText] end <- atEnd if end then return $ filter (not . blank) $ reverse (evt:T txt:s) else go (evt:T txt:s) blank (T "") = True blank (E "") = True blank (V "") = True blank _ = False var = do char '$' val <- takeTill (notInClass "a-zA-Z0-9_") return (V val) expression = do string "$(" expr <- takeTill (== ')') char ')' return (E expr)