packages feed

th-printf-0.8: src/Language/Haskell/Printf/Lib.hs

{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE QuasiQuotes #-}
{-# LANGUAGE TemplateHaskell #-}

module Language.Haskell.Printf.Lib (
  toSplices,
  OutputType (..),
) where

import Data.Maybe
import Data.String (fromString)
import GHC.Generics (Generic)
import Language.Haskell.Printf.Geometry (
  formatOne,
 )
import qualified Language.Haskell.Printf.Printers as Printers
import Language.Haskell.PrintfArg
import Language.Haskell.TH
import Language.Haskell.TH.Syntax

import Buf (
  SizedBuilder,
  SizedStr,
  finalize,
 )
import Control.Monad (mapAndUnzipM)
import Parser (parseStr)
import Parser.Types hiding (
  lengthSpec,
  width,
 )

data OutputType = OutputString | OutputText
  deriving (Show, Eq, Ord, Generic, Enum, Bounded)

{- | Takes a format string as input and produces a tuple @(args, outputExpr)@.

This function processes character escapes as they would appear in Haskell source code.
It will emit warnings (or throw an error, as appropriate) when given an invalid format
string.

Use if you wish to leverage @th-printf@ in conjunction with, for example, an existing
logging library.
-}
toSplices :: String -> OutputType -> Q ([Pat], Exp)
toSplices s' ot = case parseStr s' of
  Left x -> fail $ show x
  Right (y, warns) -> do
    mapM_ (qReport False) (concat warns)
    (lhss, rhss) <- mapAndUnzipM extractExpr y
    rhss' <-
      appE
        [|finalize|]
        (sigE (foldr1 (\x y' -> infixApp x [|(<>)|] y') rhss) otype)
    return (map VarP $ concat lhss, rhss')
 where
  otype = case ot of
    OutputString -> [t|SizedStr|]
    OutputText -> [t|SizedBuilder|]

extractExpr :: Atom -> Q ([Name], ExpQ)
extractExpr (Str s') = return ([], [|fromString $(stringE s')|])
extractExpr (Arg (FormatArg flags' width' precision' spec' lengthSpec')) = do
  (warg, wexp) <- extractArgs width'
  (parg, pexp) <- extractArgs precision'
  varg <- newName "arg"
  return
    ( catMaybes [warg, parg, Just varg]
    , appE
        [|formatOne|]
        ( appE
            formatter
            [|
              PrintfArg
                { flagSet = $(lift flags')
                , width = $(wexp)
                , prec = $(pexp)
                , value = $(varE varg)
                , lengthSpec = $(lift lengthSpec')
                , fieldSpec = $(lift spec')
                }
              |]
        )
    )
 where
  extractArgs n = case n of
    Just Need -> do
      a <- newName "arg"
      pure (Just a, [|Just (fromInteger (fromIntegral $(varE a)))|])
    Just (Given n') -> pure (Nothing, [|Just $(litE $ integerL n')|])
    Nothing -> pure (Nothing, [|Nothing|])
  formatter = case spec' of
    's' -> [|Printers.printfString|]
    'q' -> [|Printers.printfLazyText|]
    'Q' -> [|Printers.printfStrictText|]
    '?' -> [|Printers.printfShow|]
    'd' -> [|Printers.printfDecimal|]
    'i' -> [|Printers.printfDecimal|]
    'p' -> [|Printers.printfPtr|]
    'c' -> [|Printers.printfChar|]
    'u' -> [|Printers.printfUnsigned|]
    'x' -> [|Printers.printfHex False|]
    'X' -> [|Printers.printfHex True|]
    'o' -> [|Printers.printfOctal|]
    'f' -> [|Printers.printfFloating False|]
    'F' -> [|Printers.printfFloating True|]
    'e' -> [|Printers.printfScientific False|]
    'E' -> [|Printers.printfScientific True|]
    'g' -> [|Printers.printfGeneric False|]
    'G' -> [|Printers.printfGeneric True|]
    'a' -> [|Printers.printfFloatHex False|]
    'A' -> [|Printers.printfFloatHex True|]
    _ -> undefined