-- | Abstract syntax and a pretty printer for a subset of Haskell
{-# LANGUAGE DeriveFunctor #-}
module GF.Haskell where
import Prelude hiding ((<>)) -- GHC 8.4.1 clash with Text.PrettyPrint
import GF.Infra.Ident(Ident,identS)
import GF.Text.Pretty

-- | Top-level declarations
data Dec = Comment String
         | Type (ConAp Ident) Ty
         | Data (ConAp Ident) [ConAp Ty] Deriving
         | Class [ConAp Ident] (ConAp Ident) FunDeps [(Ident,Ty)]
         | Instance [Ty] Ty [(Lhs,Exp)]
         | TypeSig Ident Ty
         | Eqn Lhs Exp

-- | A type constructor applied to some arguments
data ConAp a = ConAp Ident [a] deriving Functor
conap0 n = ConAp n []
tsyn0 = Type . conap0

type Deriving = [Const]
type FunDeps = [([Ident],[Ident])]
type Lhs = (Ident,[Pat])
lhs0 s = (identS s,[])

-- | Type expressions
data Ty  = TId Ident | TAp Ty Ty | Fun Ty Ty | ListT Ty

-- | Expressions
data Exp = Var Ident | Const Const | Ap Exp Exp | Op Exp Const Exp
         | List [Exp] | Pair Exp Exp
         | Lets [(Ident,Exp)] Exp | LambdaCase [(Pat,Exp)]
type Const = String

-- | Patterns
data Pat = WildP | VarP Ident | Lit String | ConP Ident [Pat] | AsP Ident Pat

tvar = TId
tcon0 = TId
tcon c = foldl TAp (TId c)

lets [] e = e
lets ds e = Lets ds e

let1 x xe e = Lets [(x,xe)] e
single x = List [x]

plusplus (List ts1) (List ts2) = List (ts1++ts2)
plusplus (List [t]) t2 = Op t ":" t2
plusplus t1 t2 = Op t1 "++" t2

-- | Pretty print atomically (i.e. wrap it in parentheses if necessary)
class Pretty a => PPA a where ppA :: a -> Doc

instance PPA Ident where ppA = pp

instance Pretty Dec where
  ppList = vcat
  pp d =
    case d of
      Comment s -> pp s
      Type lhs rhs -> hang ("type"<+>lhs<+>"=") 4 rhs
      Data lhs cons ds ->
        hang ("data"<+>lhs) 4
             (sep (zipWith (<+>) ("=":repeat "|") cons++
                  ["deriving"<+>parens (punctuate "," ds)|not (null ds)]))
      Class ctx cls fds sigs ->
        hang ("class"<+>sep [ppctx ctx,pp cls]<+>ppfds fds <+>"where") 4
             (vcat (map ppSig sigs))
      Instance ctx inst eqns ->
        hang ("instance"<+>sep [ppctx ctx,pp inst]<+>"where") 4
             (vcat (map ppEqn eqns))
      TypeSig f ty -> hang (f<+>"::") 4 ty
      Eqn lhs rhs -> ppEqn (lhs,rhs)
    where
      ppctx ctx = case ctx of
                    [] -> empty
                    [p] -> p <+> "=>"
                    ps -> parens (fsep (punctuate "," ps)) <+> "=>"

      ppfds [] = empty
      ppfds fds = "|"<+>fsep (punctuate "," [hsep as<+>"->"<+>bs|(as,bs)<-fds])

      ppEqn ((f,ps),e) = hang (f<+>fsep (map ppA ps)<+>"=") 4 e

      ppSig (f,ty) = f<+>"::"<+>ty

instance PPA a => Pretty (ConAp a) where
  pp (ConAp c as) = c<+>fsep (map ppA as)

instance Pretty Ty where
  pp = ppT
    where
      ppT t = case flatFun t of t:ts -> sep (ppB t:["->"<+>ppB t|t<-ts])
      ppB t = case flatTAp t of t:ts -> ppA t<+>sep (map ppA ts)

      flatFun (Fun t1 t2) = t1:flatFun t2 -- right associative
      flatFun t = [t]

      flatTAp (TAp t1 t2) = flatTAp t1++[t2] -- left associative
      flatTAp t = [t]

instance PPA Ty where
  ppA t =
    case t of
      TId c -> pp c
      ListT t -> brackets t
      _ -> parens t

instance Pretty Exp where
  pp = ppT
    where
      ppT e =
        case e of
          Op e1 op e2 -> hang (ppB e1<+>op) 2 (ppB e2)
          Lets bs e -> sep ["let"<+>vcat [hang (x<+>"=") 2 xe|(x,xe)<-bs],
                            "in" <+>e]
          LambdaCase alts ->
              hang "\\case" 2 (vcat [hang (p<+>"->") 2 e|(p,e)<-alts])
          _ -> ppB e

      ppB e = case flatAp e of f:as -> hang (ppA f) 2 (sep (map ppA as))

      flatAp (Ap t1 t2) = flatAp t1++[t2] -- left associative
      flatAp t = [t]

instance PPA Exp where
  ppA e =
    case e of
      Var x -> pp x
      Const n -> pp n
      Pair e1 e2 -> parens (e1<>","<>e2)
      List es -> brackets (fsep (punctuate "," es))
      _ -> parens e

instance Pretty Pat where
  pp p =
    case p of
      ConP c ps -> c<+>fsep (map ppA ps)
      _ -> ppA p

instance PPA Pat where
  ppA p =
    case p of
      WildP -> pp "_"
      VarP x -> pp x
      Lit s -> pp s
      ConP c [] -> pp c
      AsP x p -> x<>"@"<>ppA p
      _ -> parens p
