packages feed

typelits-printf-0.3.0.0: src/GHC/TypeLits/Printf/Parse.hs

{-# LANGUAGE DataKinds #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE PolyKinds #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE TypeFamilyDependencies #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE UndecidableInstances #-}
{-# LANGUAGE NoStarIsType #-}

module GHC.TypeLits.Printf.Parse (
  ParseFmtStr,
  ParseFmtStr_,
  ParseFmt,
  ParseFmt_,
  ShowFormat,
  FormatAdjustment (..),
  FormatSign (..),
  WidthMod (..),
  Flags (..),
  EmptyFlags,
  FieldFormat (..),
  Demote,
  Reflect (..),
) where

import Data.Proxy
import Data.Text (Text)
import qualified Data.Text as T
import GHC.TypeLits hiding (natVal)
import GHC.TypeLits.Printf.Internal.Parser
import GHC.TypeNats
import Text.Printf (FormatAdjustment (..), FormatSign (..))
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 :: Char
  }

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 Char = Char
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 KnownChar c => Reflect (c :: Char) where
  reflect = charVal
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 = 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` ConsSymbol c ""
type instance
  ShowFormat ('FF f w (Just p) m c) =
    ShowFormat f
      `AppendSymbol` ShowFormat w
      `AppendSymbol` "."
      `AppendSymbol` ShowFormat p
      `AppendSymbol` ShowFormat m
      `AppendSymbol` ConsSymbol 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)) (ConsSymbol (ShowDigit (Mod n 10)) "")

type family ShowDigit (n :: Nat) :: Char 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 :: Maybe (Char, Symbol)) :: (Flags, Symbol) where
  ProcessFlags ('Flags d i l) (Just '( '-', cs)) =
    '( 'Flags (Just (UpdateAdjust d LeftAdjust)) i l, cs)
  ProcessFlags ('Flags d i l) (Just '( '0', cs)) = '( 'Flags (Just (UpdateAdjust d ZeroPad)) i l, cs)
  ProcessFlags ('Flags d i l) (Just '( '+', cs)) = '( 'Flags d (Just (UpdateSign i SignPlus)) l, cs)
  ProcessFlags ('Flags d i l) (Just '( ' ', cs)) = '( 'Flags d (Just (UpdateSign i SignSpace)) l, cs)
  ProcessFlags ('Flags d i l) (Just '( '#', cs)) = '( 'Flags d i True, cs)
  ProcessFlags f (Just '(c, cs)) = '(f, ConsSymbol c cs)
  ProcessFlags f Nothing = '(f, "")

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 =
  (AsChar 'h' *> ((WMhh <$ AsChar 'h') <|> Pure WMh))
    <|> (AsChar 'l' *> ((WMll <$ AsChar 'l') <|> Pure WMl))
    <|> (WML <$ AsChar 'L')

type FFParser =
  'FF
    <$> FlagParser
    <*> Optional Number
    <*> Optional (AsChar '.' *> Number)
    <*> Optional WMParser
    <*> AnyChar

type FmtStrParser =
  Many
    ( (Left <$> Cat (Some (NotChar '%' <|> (AsChar '%' *> AsChar '%'))))
        <|> (Right <$> (AsChar '%' *> 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