yasi-0.0.0.0: src/Yasi/Internal.hs
{-# LANGUAGE CPP #-}
-- | Internal module, no stability guarantees
module Yasi.Internal
( Segment (..),
parseSegments,
interpolator,
Stringy (..),
)
where
import Control.Monad ((>=>))
import qualified Data.ByteString as B
import qualified Data.ByteString.Lazy as BL
import qualified Data.Char as C
import qualified Data.Text as T
import qualified Data.Text.Encoding as T
import qualified Data.Text.Lazy as TL
import qualified Data.Text.Lazy.Encoding as TL
import GHC.Generics (Generic)
import qualified Language.Haskell.TH.Quote as TH
import qualified Language.Haskell.TH.Syntax as TH
#if !(MIN_VERSION_base(4,13,0))
import Control.Monad.Fail (MonadFail)
#endif
data Segment
= Lit String
| Var String
| ShowVar String
| Abs -- idea due to interpolate
deriving (Show, Eq, Generic)
parseSegments :: MonadFail m => Char -> String -> m [Segment]
parseSegments c = fmap (group []) . go
where
go s
| let (lit, rest) = span (/= c) s, not (null lit) = (Lit lit :) <$> go rest
| s == "" = pure []
| s == [c] = fail $ "should not end with single " <> [c]
| _ : c' : rest <- s, c == c' = (Lit [c] :) <$> go rest
| _ : '{' : rest <- s = case span (/= '}') rest of
(var, '}' : rest) ->
let seg = case var of
"" -> Abs
's' : 'h' : 'o' : 'w' : ' ' : var -> ShowVar var
var -> Var var
in (seg :) <$> go rest
_ -> fail "missing closing bracket"
| _ : v : rest' <- s,
isVarStartChar v =
let (vs, rest) = span isVarChar rest'
in (Var (v : vs) :) <$> go rest
| otherwise = fail $ "invalid char after " <> [c]
isVarStartChar v = C.isAscii v && C.isAlpha v
isVarChar v = C.isAscii v && (C.isAlphaNum v || v == '_' || v == '\'')
group ls [] = lit ls
group ls (Lit l : ss) = group (l : ls) ss
group ls (s : ss) = lit ls ++ (s : group [] ss)
lit [] = []
lit ls = [Lit $ mconcat $ reverse ls]
ipExpr :: TH.Exp -> TH.Exp -> [Segment] -> TH.Q TH.Exp
ipExpr cast combine segs = do
(ls, lams) <- go segs
pure $ lams $ TH.AppE combine (TH.ListE ls)
where
go = \case
[] -> pure ([], id)
(s : ss) -> prep (go ss) $ case s of
Lit l -> pure (TH.SigE (TH.LitE (TH.StringL l)) (TH.ConT ''String), id)
Var v -> pure (TH.VarE (TH.mkName v), id)
ShowVar v -> pure (TH.AppE (TH.VarE 'show) $ TH.VarE (TH.mkName v), id)
Abs -> do
n <- TH.newName "int"
pure (TH.VarE n, TH.LamE [TH.VarP n])
prep asg af = do
(as, g) <- asg
(a, f) <- af
pure (TH.AppE cast a : as, f . g)
interpolator ::
Char ->
-- | postprocess the 'Segment's
([Segment] -> TH.Q [Segment]) ->
-- | postprocess the 'TH.Exp'
(TH.Exp -> TH.Q TH.Exp) ->
TH.QuasiQuoter
interpolator c pp pp' = TH.QuasiQuoter {..}
where
quoteExp = parseSegments c >=> pp >=> ipExpr (TH.VarE 'stringy) (TH.VarE 'mconcat) >=> pp'
quotePat = const $ fail "pattern context not supported"
quoteType = const $ fail "type context not supported"
quoteDec = const $ fail "declaration context not supported"
class Stringy a b where
stringy :: a -> b
instance Stringy String String where
stringy = id
instance Stringy String T.Text where
stringy = T.pack
instance Stringy String TL.Text where
stringy = TL.pack
instance Stringy String B.ByteString where
stringy = T.encodeUtf8 . T.pack
instance Stringy String BL.ByteString where
stringy = TL.encodeUtf8 . TL.pack
instance Stringy T.Text T.Text where
stringy = id
instance Stringy T.Text String where
stringy = T.unpack
instance Stringy T.Text TL.Text where
stringy = TL.fromStrict
instance Stringy T.Text B.ByteString where
stringy = T.encodeUtf8
instance Stringy T.Text BL.ByteString where
stringy = BL.fromStrict . T.encodeUtf8
instance Stringy TL.Text TL.Text where
stringy = id
instance Stringy TL.Text String where
stringy = TL.unpack
instance Stringy TL.Text T.Text where
stringy = TL.toStrict
instance Stringy TL.Text B.ByteString where
stringy = BL.toStrict . TL.encodeUtf8
instance Stringy TL.Text BL.ByteString where
stringy = TL.encodeUtf8
instance Stringy B.ByteString B.ByteString where
stringy = id
instance Stringy BL.ByteString BL.ByteString where
stringy = id