packages feed

duet-0.0.1: src/Duet/Printer.hs

{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE KindSignatures #-}
{-# LANGUAGE ViewPatterns #-}
{-# LANGUAGE Strict #-}
{-# OPTIONS_GHC -fno-warn-name-shadowing #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE LambdaCase #-}

-- |

module Duet.Printer where

import           Data.Char
import           Data.List
import qualified Data.Map.Strict as M
import           Duet.Types
import           Text.Printf

class PrintableType (t :: * -> *) where
  printType :: Printable i => Print i l -> SpecialTypes i -> t i -> String

instance PrintableType (Predicate Type) where
  printType = printPredicate

class (Eq a, Identifiable a) => Printable a where
  printit :: Print i l -> a -> String

instance Printable Name where
  printit printer =
    \case
      PrimopName primop -> printPrimop primop
      ValueName i string ->
        string ++
        (if printNameDetails printer
           then "[value:" ++ show i ++ "]"
           else "")
      TypeName i string ->
        string ++
        (if printNameDetails printer
           then "[type:" ++ show i ++ "]"
           else "")
      ConstructorName i string ->
        string ++
        (if printNameDetails printer
           then "[constructor:" ++ show i ++ "]"
           else "")
      ForallName i -> "g" ++ show i
      DictName i string ->
        string ++
        (if printNameDetails printer
           then "[dict:" ++ show i ++ "]"
           else "")
      ClassName i s ->
        s ++
        (if printNameDetails printer
           then "[class:" ++ show i ++ "]"
           else "")
      MethodName i s ->
        s ++
        (if printNameDetails printer
           then "[method:" ++ show i ++ "]"
           else "")

printPrimop :: Primop -> [Char]
printPrimop =
  \case
    PrimopIntegerSubtract -> "subtract"
    PrimopIntegerTimes -> "times"
    PrimopIntegerPlus -> "plus"
    PrimopRationalSubtract -> "subtract"
    PrimopRationalTimes -> "times"
    PrimopRationalPlus -> "plus"
    PrimopRationalDivide -> "divide"
    PrimopStringAppend -> "append"

instance Printable Identifier where
  printit _ =
    \case
      Identifier string -> string

defaultPrint :: Print i b
defaultPrint =
  Print
  { printDictionaries = False
  , printTypes = const Nothing
  , printNameDetails = False
  }

data Print i l = Print
  { printTypes :: (l -> Maybe (SpecialTypes i, TypeSignature Type i ()))
  , printDictionaries :: Bool
  , printNameDetails :: Bool
  }

printDataType :: (Printable i, PrintableType t) => Print i l -> SpecialTypes i -> DataType t i -> String
printDataType printer specialTypes (DataType name vars cons) =
  "data " ++ printit printer name ++ " " ++ unwords (map (printTypeVariable printer) vars) ++ "\n  = " ++
    intercalate "\n  | " (map (printConstructor printer specialTypes) cons)

printConstructor :: (Printable i, PrintableType t) => Print i l ->  SpecialTypes i -> DataTypeConstructor t i -> [Char]
printConstructor printer specialTypes (DataTypeConstructor name fields) =
  printit printer name ++ " " ++ unwords (map (printType printer specialTypes) fields)

printTypeSignature
  :: (Printable i, Printable j)
  => Print i l ->  SpecialTypes i -> TypeSignature Type i j -> String
printTypeSignature printer specialTypes (TypeSignature thing scheme) =
  printit printer thing ++ " :: " ++ printScheme printer specialTypes scheme

printIdentifier :: Printable j => Print i l ->  j -> String
printIdentifier printer = printit printer

printImplicitlyTypedBinding
  :: (Printable i, PrintableType t)
  => Print i l -> ImplicitlyTypedBinding t i l -> String
printImplicitlyTypedBinding printer (ImplicitlyTypedBinding _ (i, _) [alt]) =
  printIdentifier printer i ++ " " ++ printAlternative printer alt
printImplicitlyTypedBinding _ _ = ""

printExplicitlyTypedBinding
  :: (Printable i, PrintableType t)
  => Print i l -> SpecialTypes i -> ExplicitlyTypedBinding t i l -> String
printExplicitlyTypedBinding printer specialTypes (ExplicitlyTypedBinding _ (i, _) scheme [alt]) =
  printIdentifier printer i ++ " :: " ++ printScheme printer specialTypes scheme ++ "\n" ++
  printIdentifier printer i ++ " " ++ printAlternative printer alt
printExplicitlyTypedBinding _ _ _ = ""

printAlternative :: (Printable i, PrintableType t) => Print i l -> Alternative t i l -> [Char]
printAlternative printer (Alternative _ patterns expression) =
  concat (map (\p->printPattern printer p ++ " ") patterns) ++ "= " ++ printExpression printer expression

printPattern :: (Printable i, PrintableType t) => Print i l ->  Pattern t i l -> [Char]
printPattern printer =
  \case
    BangPattern p -> "!" ++ printPattern printer p
    VariablePattern _ i -> printIdentifier printer i
    WildcardPattern _ s -> s
    AsPattern _ i p -> printIdentifier printer i ++ "@" ++ printPattern printer p
    LiteralPattern _ l -> printLiteral l
    ConstructorPattern _ i pats ->
      printIdentifier printer i ++ " " ++ unwords (map (printPattern printer) pats)

printExpression :: (Printable i, PrintableType t) => Print i l -> (Expression t i l) -> String
printExpression printer e =
  wrapType
    (case e of
       LiteralExpression _ l -> printLiteral l
       VariableExpression _ i -> printIdentifier printer i
       ConstantExpression _ i -> printIdentifier printer i
       ConstructorExpression _ i -> printIdentifier printer i
       ParensExpression _ e -> "(" <> (printExpression printer e) <> ")"
       CaseExpression _ e alts ->
         "case " ++
         indent 5 (printExpressionIfPred printer e) ++
         " of\n" ++ indented (intercalate "\n" (map (printAlt printer) alts))
       ApplicationExpression _ f x ->
         case x of
           VariableExpression _ (nonrenamableName -> Just (DictName {}))
             | not (printDictionaries printer) -> printExpressionAppOp printer f
           _ ->
             if any (== '\n') inner || any (== '\n') prefix
               then prefix ++ "\n" ++ indented inner
               else prefix ++ " " ++ indent (length prefix + 1) inner
             where prefix = printExpressionAppOp printer f
                   inner = printExpressionAppArg printer x
       LambdaExpression _ (Alternative _ args e) ->
         if null filteredArgs
           then inner
           else if any (== '\n') inner
                  then "\\" ++ prefix ++ "->\n" ++ indented inner
                  else "\\" ++
                       prefix ++ "-> " ++ indent (length prefix + 4) inner
         where inner = (printExpression printer e)
               filteredArgs = filter dictPred args
               prefix =
                 concat (map (\x -> printPattern printer x ++ " ") filteredArgs)
               dictPred =
                 if printDictionaries printer
                   then const True
                   else \case
                          VariablePattern _ (nonrenamableName -> Just (DictName {})) ->
                            False
                          _ -> True
       IfExpression _ a b c ->
         "if " ++
         printExpressionIfPred printer a ++
         " then " ++
         printExpression printer b ++ " else " ++ printExpression printer c
       InfixExpression _ f (o, ov) x ->
         printExpressionAppArg printer f ++
         " " ++
         (if printDictionaries printer
            then "`" ++ printExpression printer ov ++ "`"
            else o) ++
         " " ++ printExpressionAppArg printer x
       _ -> "<TODO>")
  where
    wrapType x =
      case printTypes printer (expressionLabel e) of
        (Nothing) -> x
        (Just (specialTypes, TypeSignature _ ty)) ->
          "(" ++
          parens x ++ " :: " ++ printScheme printer specialTypes ty ++ ")"
          where parens k =
                  if any isSpace k
                    then "(" ++ k ++ ")"
                    else k

printAlt
  :: (PrintableType t, Printable i)
  => Print i l -> (CaseAlt t i l) -> [Char]
printAlt printer =
  \(CaseAlt _ p e') ->
    let inner = printExpression printer e'
    in if any (== '\n') inner
         then printPat printer p ++ " ->\n" ++ indented inner
         else printPat printer p ++ " -> " ++ indent 2 inner

indented :: String -> [Char]
indented x = intercalate "\n" (map ("  "++) (lines x))

indent :: Int -> String -> [Char]
indent n = intercalate ("\n" ++ replicate n ' ') . lines

lined :: [[Char]] -> [Char]
lined = intercalate "\n  "

printPat :: (Printable i, PrintableType t) => Print i l ->  Pattern t i l -> String
printPat printer=
  \case
    BangPattern p -> "!" ++ printPat printer p
    VariablePattern _ i -> printit printer i
    ConstructorPattern _ i ps ->
      printit printer i ++
      (if null ps
         then ""
         else " " ++ unwords (map inner ps))
    WildcardPattern{} -> "_"
    AsPattern _ ident p -> printit printer ident ++ "@" ++ printPat printer p
    LiteralPattern _ l -> printLiteral l
  where
    inner =
      \case
        BangPattern p -> "!" ++ inner p
        VariablePattern _ i -> printit printer i
        WildcardPattern _ s -> s
        ConstructorPattern _ i ps
          | null ps -> printit printer i
          | otherwise ->
            "(" ++ printit printer i ++ " " ++ unwords (map inner ps) ++ ")"
        AsPattern _ ident p -> printit printer ident ++ "@" ++ printPat printer p
        LiteralPattern _ l -> printLiteral l

printExpressionAppArg :: (Printable i, PrintableType t) => Print i l ->(Expression t i l) -> String
printExpressionAppArg printer =
  \case
    e@(ApplicationExpression {})
      | nodict e -> paren (printExpression printer e)
    e@(IfExpression {}) -> paren (printExpression printer e)
    e@(InfixExpression {}) -> paren (printExpression printer e)
    e@(LambdaExpression {}) -> paren (printExpression printer e)
    e@(CaseExpression {}) -> paren (printExpression printer e)
    e -> printExpression printer e
  where
    nodict =
      \case
        ApplicationExpression _ _ (VariableExpression _ (nonrenamableName -> Just (DictName {})))
          | not (printDictionaries printer) -> False
        _ -> True

printExpressionIfPred :: (Printable i, PrintableType t) => Print i l -> (Expression t i l) -> String
printExpressionIfPred printer=
  \case
    e@(IfExpression {}) -> paren (printExpression printer e)
    e@(LambdaExpression {}) -> paren (printExpression printer e)
    e@(CaseExpression {}) -> paren (printExpression printer e)
    e -> printExpression printer e

printExpressionAppOp :: (Printable i, PrintableType t) => Print i l -> (Expression t i l) -> String
printExpressionAppOp printer=
  \case
    e@(IfExpression {}) -> paren (printExpression printer e)
    e@(LambdaExpression {}) -> paren (printExpression printer e)
    e@(CaseExpression {}) -> paren (printExpression printer e)
    e -> printExpression printer e

paren :: [Char] -> [Char]
paren e = "("  ++ indent 1 e ++ ")"

printLiteral :: Literal -> String
printLiteral (IntegerLiteral i) = show i
printLiteral (RationalLiteral i) = printf "%f" (fromRational i :: Double)
printLiteral (StringLiteral x) = show x
printLiteral (CharacterLiteral x) = show x

printScheme :: (Printable i, PrintableType t, PrintableType t1) => Print i l -> SpecialTypes i -> Scheme t i t1 -> [Char]
printScheme printer specialTypes (Forall kinds qualifiedType') =
  (if null kinds
     then ""
     else "forall " ++
          unwords
            (zipWith
               (\_i k ->
                  printTypeVariable
                    (Print
                     { printTypes = const Nothing
                     , printDictionaries = False
                     , printNameDetails = printNameDetails printer
                     })
                    k)
               [0 :: Int ..]
               kinds) ++
          ". ") ++
  printQualifiedType specialTypes qualifiedType'
  where
    printQualifiedType specialTypes (Qualified predicates typ) =
      case predicates of
        [] -> printType printer specialTypes typ
        _ ->
          "(" ++
          intercalate
            ", "
            (map (printPredicate printer specialTypes) predicates) ++
          ") => " ++ printType printer specialTypes typ


printClass :: Printable i => Print i l -> SpecialTypes i -> Class Type i l -> String
printClass printer specialTypes (Class vars supers instances i methods) =
  "class " ++
  printSupers printer specialTypes supers ++
  printit printer i ++
  " " ++
  unwords (map (printTypeVariable printer) vars) ++ " where\n  " ++
  intercalate "\n  " (map (printMethod printer specialTypes) (M.toList methods)) ++
  "\n" ++ intercalate "\n" (map (printInstance printer specialTypes) instances)

printMethod :: Printable i =>  Print i l -> SpecialTypes i -> (i, Scheme Type i Type) -> String
printMethod printer specialTypes (i, scheme) =
  printit printer i ++ " :: " ++ printScheme printer specialTypes scheme

printInstance :: Printable i => Print i l -> SpecialTypes i -> Instance Type i l -> String
printInstance printer specialTypes (Instance scheme _) =
  "instance " ++
  printScheme printer specialTypes scheme

printSupers :: Printable i => Print i l -> SpecialTypes i -> [Predicate Type i] -> [Char]
printSupers printer specialTypes supers
  | null supers = ""
  | otherwise =
    "(" ++ intercalate ", " (map (printPredicate printer specialTypes) supers) ++ ") => "


printPredicate :: (Printable i, PrintableType t) => Print i l -> SpecialTypes i -> Predicate t i -> [Char]
printPredicate printer specialTypes (IsIn identifier types) =
  printIdentifier printer identifier ++
  " " ++ unwords (map (wrap . printType printer specialTypes) types)
  where wrap x = if any isSpace x
                    then "(" ++ x ++ ")"
                    else x

printKind :: Kind -> [Char]
printKind =
  \case
    StarKind -> "Type"
    FunctionKind x' y -> printKind x' ++ " -> " ++ printKind y

printTypeSansParens :: (Printable i) => Print i l ->  SpecialTypes i -> Type i -> [Char]
printTypeSansParens printer specialTypes =
  \case
    ApplicationType (ApplicationType func x') y'
      | func == ConstructorType (specialTypesFunction specialTypes) ->
        printType printer specialTypes x' ++
        " -> " ++ printTypeSansParens printer specialTypes y'
    o -> printType printer specialTypes o

instance PrintableType Type where
  printType printer specialTypes =
    \case
      VariableType v -> printTypeVariable printer v
      ConstructorType tyCon -> printTypeConstructor printer tyCon
      ApplicationType (ApplicationType func x') y
        | func == ConstructorType (specialTypesFunction specialTypes) ->
          "(" ++
          printType printer specialTypes x' ++
          " -> " ++ printTypeSansParens printer specialTypes y ++ ")"
    -- ApplicationType list ty | list == specialTypesList specialTypes ->
    --   "[" ++ printTypeSansParens specialTypes ty ++ "]"
      ApplicationType x' y ->
        printType printer specialTypes x' ++ " " ++ printTypeArg y
      -- GenericType int -> "g" ++ show int
    where
      printTypeArg =
        \case
          x@ApplicationType {} -> "(" ++ printType printer specialTypes x ++ ")"
          x -> printType printer specialTypes x

instance PrintableType UnkindedType where
  printType printer specialTypes =
    \case
      UnkindedTypeVariable v -> printIdentifier printer v
      UnkindedTypeConstructor tyCon -> printIdentifier printer tyCon
      UnkindedTypeApp x' y ->
        "(" ++ printType printer specialTypes x' ++ " " ++ printType printer specialTypes y ++ ")"

printTypeConstructor :: Printable j => Print i l -> TypeConstructor j -> String
printTypeConstructor printer (TypeConstructor identifier kind) =
  case kind of
    StarKind -> printIdentifier printer identifier
    FunctionKind {} -> printIdentifier printer identifier
        -- _ -> "(" ++ printIdentifier identifier ++ " :: " ++ printKind kind ++ ")"

printTypeVariable :: Printable i => Print i l -> TypeVariable i -> String
printTypeVariable printer (TypeVariable identifier kind) =
  case kind of
    StarKind -> printIdentifier printer identifier
    _ -> "(" ++ printIdentifier printer identifier ++ " :: " ++ printKind kind ++ ")"

curlyQuotes :: [Char] -> [Char]
curlyQuotes t = "‘" <> t <> "’"