exon-0.3.0.0: lib/Exon/Quote.hs
{-# options_haddock prune #-}
-- |Description: Internal
module Exon.Quote where
import Language.Haskell.Exts (
Extension,
ParseMode (extensions),
ParseResult (ParseFailed, ParseOk),
defaultParseMode,
parseExpWithMode,
parseExtension,
)
import Language.Haskell.Meta (toExp)
import qualified Language.Haskell.TH as TH
import Language.Haskell.TH (Exp (AppE, InfixE, ListE), Q, extsEnabled, runQ)
import Language.Haskell.TH.Quote (QuasiQuoter (QuasiQuoter))
import Language.Haskell.TH.Syntax (Quasi)
import Exon.Class.Exon (ExonDefault, KeepWhitespace, concatSegments)
import Exon.Data.RawSegment (RawSegment (ExpSegment, StringSegment, WsSegment))
import qualified Exon.Data.Segment as Segment
import Exon.Parse (parse)
exonError ::
ToString e =>
MonadFail m =>
e ->
m a
exonError err =
fail ("Exon: " <> toString err)
segmentsQ ::
QOrIO m =>
String ->
m (NonEmpty RawSegment)
segmentsQ =
parse >>> fmap nonEmpty >>> \case
Right (Just segs) -> pure segs
Right Nothing -> pure (pure (StringSegment ""))
Left err -> exonError err
class Quasi m => QOrIO (m :: Type -> Type) where
fileExtensions :: m [Extension]
instance QOrIO IO where
fileExtensions =
pure []
instance QOrIO Q where
fileExtensions =
fmap (fmap (parseExtension . show)) extsEnabled
reifyExp ::
QOrIO m =>
String ->
m Exp
reifyExp s = do
exts <- fileExtensions
case parseExpWithMode defaultParseMode { extensions = exts } s of
ParseFailed _ err -> exonError err
ParseOk e -> pure (toExp e)
reifySegments ::
QOrIO m =>
NonEmpty RawSegment ->
m (NonEmpty Exp)
reifySegments segs = do
expCon <- runQ [e|Segment.Expression|]
for segs \case
StringSegment s ->
runQ [e|Segment.String s|]
ExpSegment s -> do
e <- reifyExp s
pure (AppE expCon e)
WsSegment s ->
runQ [e|Segment.Whitespace s|]
quoteExpWith ::
QOrIO m =>
Q TH.Type ->
String ->
m Exp
quoteExpWith tag code = do
raw <- segmentsQ code
hseg :| segs <- reifySegments raw
conc <- runQ [e|concatSegments @($tag)|]
consE <- runQ [e|(:|)|]
pure (AppE conc (InfixE (Just hseg) consE (Just (ListE segs))))
quoteExp ::
QOrIO m =>
String ->
m Exp
quoteExp =
quoteExpWith [t|ExonDefault|]
-- |Constructor for a quasiquoter for an arbitrary tag.
--
-- This can be used to define quoters with custom logic, requiring an instance of 'Exon.Class.Exon' for the given type:
--
-- >>> import Exon.Class.Exon (Exon(..))
-- >>> import Exon.Data.Segment (Segment(String))
-- >>> data Nl
-- >>> instance (Monoid a, IsString a) => Exon Nl a where insertWhitespace s1 _ s2 = appendSegment @Nl (appendSegment @Nl s1 (String "\n")) s2
-- >>> exonnl = exonWith [t|Nl|]
-- >>> [exonnl|one two three|]
-- "one\ntwo\nthree"
--
-- @since 0.2.0.0
exonWith :: Q TH.Type -> QuasiQuoter
exonWith tag =
QuasiQuoter (quoteExpWith tag) (err "pattern") (err "type") (err "decl")
where
err :: String -> String -> Q a
err tpe _ =
exonError ("Cannot quote " <> tpe)
-- |A quasiquoter that allows interpolation, concatenating the resulting segments monoidally.
--
-- >>> [exon|write #{show @Text (5 :: Int)} lines of code|] :: Text
-- "write 5 lines of code"
--
-- The default implementation for any non-stringly type uses 'IsString' to construct the literal segments and 'mappend'
-- to combine them, ignoring whitespace segments.
--
-- >>> newtype Part = Part Text deriving newtype (Show, Semigroup, Monoid, IsString)
--
-- >>> [exon|x #{Part "y"}z|] :: Part
-- "xyz"
--
-- This behavior can be customized by writing an instance of 'Exon.Exon'.
exon :: QuasiQuoter
exon =
exonWith [t|ExonDefault|]
-- |A variant of 'exon' that always keeps whitespace verbatim.
--
-- @since 0.2.0.0
exonws :: QuasiQuoter
exonws =
exonWith [t|KeepWhitespace|]