
module Printf where

_∘_ : {A : Set}{B : A -> Set}{C : {x : A} -> B x -> Set} ->
      (f : {x : A}(y : B x) -> C y)(g : (x : A) -> B x)(x : A) -> C (g x)
(f ∘ g) x = f (g x)

infixr 10 _::_
data List (A : Set) : Set where
  nil  : List A
  _::_ : A -> List A -> List A

{-# BUILTIN LIST    List   #-}

[_] : {A : Set} -> A -> List A
[ x ] = x :: nil

module Primitive where

  data Nat : Set where
    zero : Nat
    suc  : Nat → Nat

  data Int : Set where
    pos    : Nat → Int
    negsuc : Nat → Int

  postulate
    String : Set
    Float  : Set
    Char   : Set

  {-# BUILTIN NATURAL Nat    #-}
  {-# BUILTIN INTEGER Int    #-}
  {-# BUILTIN INTEGERPOS pos #-}
  {-# BUILTIN INTEGERNEGSUC negsuc #-}
  {-# BUILTIN STRING  String #-}
  {-# BUILTIN FLOAT   Float  #-}
  {-# BUILTIN CHAR    Char   #-}

  private
    primitive
      primStringAppend   : String -> String -> String
      primStringToList   : String -> List Char
      primStringFromList : List Char -> String
      primShowChar       : Char -> String
      primShowInteger    : Int -> String
      primShowFloat      : Float -> String

  _++_         = primStringAppend
  showChar     = primShowChar
  showInt      = primShowInteger
  showFloat    = primShowFloat
  stringToList = primStringToList
  listToString = primStringFromList

open Primitive

data Unit : Set where
  unit : Unit

infixr 8 _×_
infixr 8 _◅_

data _×_ (A B : Set) : Set where
  _◅_ : A -> B -> A × B

data Format : Set where
  stringArg : Format
  intArg    : Format
  floatArg  : Format
  charArg   : Format
  litChar   : Char -> Format
  badFormat : Char -> Format

data BadFormat (c : Char) : Set where

format : String -> List Format
format = format' ∘ stringToList
  where
    format' : List Char -> List Format
    format' ('%' :: 's' :: fmt) = stringArg   :: format' fmt
    format' ('%' :: 'd' :: fmt) = intArg      :: format' fmt
    format' ('%' :: 'f' :: fmt) = floatArg    :: format' fmt
    format' ('%' :: 'c' :: fmt) = charArg     :: format' fmt
    format' ('%' :: '%' :: fmt) = litChar '%' :: format' fmt
    format' ('%' ::  c  :: fmt) = badFormat c :: format' fmt
    format' (c          :: fmt) = litChar c   :: format' fmt
    format'  nil                = nil

Printf' : List Format -> Set
Printf' (stringArg   :: fmt) = String  × Printf' fmt
Printf' (intArg      :: fmt) = Int     × Printf' fmt
Printf' (floatArg    :: fmt) = Float   × Printf' fmt
Printf' (charArg     :: fmt) = Char    × Printf' fmt
Printf' (badFormat c :: fmt) = BadFormat c
Printf' (litChar _   :: fmt) = Printf' fmt
Printf'  nil                 = Unit

Printf : String -> Set
Printf fmt = Printf' (format fmt)

printf : (fmt : String) -> Printf fmt -> String
printf = printf' ∘ format
  where
    printf' : (fmt : List Format) -> Printf' fmt -> String
    printf' (stringArg   :: fmt) (s ◅ args) = s                  ++ printf' fmt args
    printf' (intArg      :: fmt) (n ◅ args) = showInt n          ++ printf' fmt args
    printf' (floatArg    :: fmt) (x ◅ args) = showFloat x        ++ printf' fmt args
    printf' (charArg     :: fmt) (c ◅ args) = showChar c         ++ printf' fmt args
    printf' (litChar c   :: fmt) args       = listToString [ c ] ++ printf' fmt args
    printf' (badFormat _ :: fmt) ()
    printf'  nil                 unit       = ""

