typelits-printf-0.1.0.0: src/GHC/TypeLits/Printf/Parse.hs
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE KindSignatures #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE NoStarIsType #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE TypeFamilyDependencies #-}
{-# LANGUAGE TypeInType #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE UndecidableInstances #-}
module GHC.TypeLits.Printf.Parse (
ParseFmtStr
, ParseFmtStr_
, ParseFmt
, ParseFmt_
, ShowFormat
, FormatAdjustment(..)
, FormatSign(..)
, WidthMod(..)
, Flags(..)
, EmptyFlags
, FieldFormat(..)
, SChar
, Demote
, Reflect(..)
) where
import Data.Proxy
import Data.Text (Text)
import GHC.TypeLits hiding (natVal)
import GHC.TypeLits.Printf.Internal.Parser
import GHC.TypeNats
import Numeric.Natural
import Text.Printf (FormatAdjustment(..), FormatSign(..))
import qualified Data.Text as T
import qualified Text.Printf as P
-- hello, we're going to attempt to implement
-- https://docs.microsoft.com/en-us/cpp/c-runtime-library/format-specification-syntax-printf-and-wprintf-functions?view=vs-2019
data Flags = Flags
{ fAdjust :: Maybe FormatAdjustment
, fSign :: Maybe FormatSign
, fAlternate :: Bool
}
data WidthMod = WMhh
| WMh
| WMl
| WMll
| WML
data FieldFormat = FF
{ fmtFlags :: Flags
, fmtWidth :: Maybe Nat
, fmtPrecision :: Maybe Nat
, fmtWidthMod :: Maybe WidthMod
, fmtChar :: SChar
}
type family Demote k = a | a -> k
type instance Demote FormatAdjustment = FormatAdjustment
type instance Demote FormatSign = FormatSign
type instance Demote Bool = Bool
type instance Demote (Maybe a) = Maybe (Demote a)
type instance Demote Nat = Natural
type instance Demote Symbol = Text
type instance Demote Flags = Flags
type instance Demote WidthMod = WidthMod
type instance Demote FieldFormat = P.FieldFormat
class Reflect (x :: a) where
reflect :: p x -> Demote a
instance Reflect 'LeftAdjust where
reflect _ = LeftAdjust
instance Reflect 'ZeroPad where
reflect _ = ZeroPad
instance Reflect 'SignPlus where
reflect _ = SignPlus
instance Reflect 'SignSpace where
reflect _ = SignSpace
instance Reflect 'WMhh where
reflect _ = WMhh
instance Reflect 'WMh where
reflect _ = WMh
instance Reflect 'WMl where
reflect _ = WMl
instance Reflect 'WMll where
reflect _ = WMll
instance Reflect 'WML where
reflect _ = WML
instance Reflect 'False where
reflect _ = False
instance Reflect 'True where
reflect _ = True
instance Reflect 'Nothing where
reflect _ = Nothing
instance Reflect x => Reflect ('Just x) where
reflect _ = Just (reflect (Proxy @x))
instance KnownNat n => Reflect (n :: Nat) where
reflect = natVal
instance KnownSymbol n => Reflect (n :: Symbol) where
reflect = T.pack . symbolVal
instance (Reflect d, Reflect i, Reflect l) => Reflect ('Flags d i l) where
reflect _ = Flags (reflect (Proxy @d))
(reflect (Proxy @i))
(reflect (Proxy @l))
instance (Reflect flags, Reflect width, Reflect prec, Reflect mods, Reflect chr)
=> Reflect ('FF flags width prec mods chr) where
reflect _ = P.FieldFormat{..}
where
Flags{..} = reflect (Proxy @flags)
fmtWidth = fromIntegral <$> reflect (Proxy @width)
fmtPrecision = fromIntegral <$> reflect (Proxy @prec)
fmtAdjust = fAdjust
fmtSign = fSign
fmtAlternate = fAlternate
fmtModifiers = foldMap modString (reflect (Proxy @mods))
fmtChar = T.head (reflect (Proxy @chr))
type family ShowFormat (x :: k) :: Symbol
type instance ShowFormat 'LeftAdjust = "-"
type instance ShowFormat 'ZeroPad = "0"
type instance ShowFormat 'SignPlus = "+"
type instance ShowFormat 'SignSpace = " "
type instance ShowFormat 'Nothing = ""
type instance ShowFormat ('Just x) = ShowFormat x
type instance ShowFormat ('Flags a s 'False) = ShowFormat a `AppendSymbol` ShowFormat s
type instance ShowFormat ('Flags a s 'True ) = ShowFormat a `AppendSymbol` ShowFormat s `AppendSymbol` "#"
type instance ShowFormat 'WMhh = "hh"
type instance ShowFormat 'WMh = "h"
type instance ShowFormat 'WMl = "l"
type instance ShowFormat 'WMll = "ll"
type instance ShowFormat 'WML = "L"
type instance ShowFormat (n :: Nat) = ShowNat n
type instance ShowFormat ('FF f w 'Nothing m c) = ShowFormat f
`AppendSymbol` ShowFormat w
`AppendSymbol` ShowFormat m
`AppendSymbol` c
type instance ShowFormat ('FF f w ('Just p) m c) = ShowFormat f
`AppendSymbol` ShowFormat w
`AppendSymbol` "."
`AppendSymbol` ShowFormat p
`AppendSymbol` ShowFormat m
`AppendSymbol` c
type family ShowNat (n :: Nat) :: Symbol where
ShowNat 0 = "0"
ShowNat n = ShowNatHelp n
type family ShowNatHelp (n :: Nat) :: Symbol where
ShowNatHelp 0 = ""
ShowNatHelp n = AppendSymbol (ShowNatHelp (Div n 10)) (ShowDigit (Mod n 10))
type family ShowDigit (n :: Nat) :: SChar where
ShowDigit 0 = "0"
ShowDigit 1 = "1"
ShowDigit 2 = "2"
ShowDigit 3 = "3"
ShowDigit 4 = "4"
ShowDigit 5 = "5"
ShowDigit 6 = "6"
ShowDigit 7 = "7"
ShowDigit 8 = "8"
ShowDigit 9 = "9"
modString :: WidthMod -> String
modString = \case
WMhh -> "hh"
WMh -> "h"
WMl -> "l"
WMll -> "ll"
WML -> "L"
data FlagParser :: Parser Flags
type instance RunParser FlagParser str = 'Just (ProcessFlags EmptyFlags str)
type EmptyFlags = 'Flags 'Nothing 'Nothing 'False
type family ProcessFlags (f :: Flags) (str :: [SChar]) :: (Flags, [SChar]) where
ProcessFlags ('Flags d i l) ("-" ': cs) = '( 'Flags ('Just (UpdateAdjust d 'LeftAdjust)) i l, cs)
ProcessFlags ('Flags d i l) ("0" ': cs) = '( 'Flags ('Just (UpdateAdjust d 'ZeroPad )) i l, cs)
ProcessFlags ('Flags d i l) ("+" ': cs) = '( 'Flags d ('Just (UpdateSign i 'SignPlus )) l, cs)
ProcessFlags ('Flags d i l) (" " ': cs) = '( 'Flags d ('Just (UpdateSign i 'SignSpace)) l, cs)
ProcessFlags ('Flags d i l) ("#" ': cs) = '( 'Flags d i 'True, cs)
ProcessFlags f cs = '(f, cs)
type family UpdateAdjust d1 d2 where
UpdateAdjust 'Nothing d2 = d2
UpdateAdjust ('Just 'LeftAdjust) d2 = 'LeftAdjust
UpdateAdjust ('Just 'ZeroPad ) d2 = d2
type family UpdateSign i1 i2 where
UpdateSign 'Nothing i2 = i2
UpdateSign ('Just 'SignPlus ) i2 = 'SignPlus
UpdateSign ('Just 'SignSpace) i2 = i2
type WMParser = (Sym "h" *> (('WMhh <$ Sym "h") <|> Pure 'WMh))
<|> (Sym "l" *> (('WMll <$ Sym "l") <|> Pure 'WMl))
<|> ('WML <$ Sym "L")
type FFParser = 'FF <$> FlagParser
<*> Optional Number
<*> Optional (Sym "." *> Number)
<*> Optional WMParser
<*> AnySym
-- <*> Alpha -- which of these is right?
type FmtStrParser = Many ( ('Left <$> Cat (Some (NotSym "%" <|> (Sym "%" *> Sym "%"))))
<|> ('Right <$> (Sym "%" *> FFParser))
)
type ParseFmtStr str = EvalParser FmtStrParser str
type ParseFmtStr_ str = EvalParser_ FmtStrParser str
type ParseFmt str = EvalParser FFParser str
type ParseFmt_ str = EvalParser_ FFParser str