typist-0.1.0.0: src/Typist/Internal/Format.hs
{-# LANGUAGE GADTs #-}
{-# LANGUAGE PolyKinds #-}
module Typist.Internal.Format (module Typist.Internal.Format) where
import Data.Data (Proxy (..))
import Data.String (IsString (..))
import qualified Data.Text.Lazy.Builder as Builder
import GHC.TypeLits (
ConsSymbol,
ErrorMessage (..),
KnownSymbol,
Nat,
Symbol,
TypeError,
UnconsSymbol,
symbolVal,
type (+),
)
import GHC.TypeNats (KnownNat, natVal)
type family Format (str :: Symbol) where
Format str = ContFormat 0 (UnconsSymbol str)
newtype Arg (n :: Nat) (s :: Symbol) = Arg Builder.Builder
type family ContFormat (n :: Nat) (a :: Maybe (Char, Symbol)) where
ContFormat n ('Just '( '\\', rest)) = SkipOne (n + 1) (UnconsSymbol rest)
ContFormat n ('Just '( '#', rest)) = TryGetArg n (UnconsSymbol rest)
ContFormat n ('Just '(a, rest)) = ContFormat (n + 1) (UnconsSymbol rest)
ContFormat n 'Nothing = '[]
type family TryGetArg n rest where
TryGetArg n ('Just '( '{', rest)) =
Arg n (TakeName (UnconsSymbol rest))
': ContFormat (n + 2) (UnconsSymbol (SkipName (UnconsSymbol rest)))
TryGetArg n 'Nothing = ContFormat n 'Nothing
TryGetArg n ('Just '(a, rest)) = ContFormat (n + 2) (UnconsSymbol rest)
type family SkipOne (n :: Nat) (s :: Maybe (Char, Symbol)) where
SkipOne n 'Nothing = ContFormat n 'Nothing
SkipOne n ('Just '(a, rest)) = ContFormat (n + 1) (UnconsSymbol rest)
type family TakeName (a :: Maybe (Char, Symbol)) :: Symbol where
TakeName ('Just '( '}', rest)) = ""
TakeName ('Just '(a, rest)) = ConsSymbol a (TakeName (UnconsSymbol rest))
TakeName 'Nothing = TypeError ('Text "Expected '}' but EOF found. Close placeholder with '}'. Example: #{name}")
type family SkipName (a :: Maybe (Char, Symbol)) :: Symbol where
SkipName ('Just '( '}', rest)) = rest
SkipName ('Just '(a, rest)) = SkipName (UnconsSymbol rest)
SkipName 'Nothing = ""
class Interpolate args where
interpolate :: Rec args -> Int -> String -> Builder.Builder -> Builder.Builder
instance Interpolate '[] where
{-# INLINE interpolate #-}
interpolate _ _ string acc = acc <> fromString string
instance (Interpolate args, KnownNat i) => Interpolate (Arg i n ': args) where
{-# INLINE interpolate #-}
interpolate (Arg s :& record) start string acc =
interpolate @args
record
(nVal + 2)
(drop 1 $ dropWhile (/= '}') $ drop (diff + 3) string)
(acc <> fromString (take diff string) <> s)
where
nVal = fromIntegral $ natVal (Proxy @i)
diff = nVal - start
data Rec as where
RNil :: Rec '[]
(:&) :: Arg n s -> Rec ns -> Rec (Arg n s ': ns)
-- | See usage example next to @'Typist.TextShow.#='@ at "Typist.TextShow"
{-# INLINE fmt #-}
fmt :: forall str. (KnownSymbol str, Interpolate (Format str)) => (Rec '[] -> Rec (Format str)) -> Builder.Builder
fmt record_ = interpolate @(Format str) (record_ RNil) 0 (symbolVal (Proxy @str)) mempty