{-# LANGUAGE CPP #-}
-----------------------------------------------------------------------------
-- |
-- Module      : Language.Python.Version2.Syntax.PrettyAST
-- Copyright   : (c) 2009 Bernie Pope 
-- License     : BSD-style
-- Maintainer  : bjpop@csse.unimelb.edu.au
-- Stability   : experimental
-- Portability : ghc
--
-- Pretty printing of the Python abstract syntax (version 2.x and 3.x). 
-----------------------------------------------------------------------------

module Language.Python.Common.PrettyAST () where

#if __GLASGOW_HASKELL__ >= 803
import Prelude hiding ((<>))
#endif

import Language.Python.Common.Pretty
import Language.Python.Common.AST
import Data.Maybe (fromMaybe)

--------------------------------------------------------------------------------

dot :: Doc
dot = char '.'

indent :: Doc -> Doc
indent doc = nest 4 doc

-- XXX is there a better way to do this?
blankLine :: Doc
blankLine = text []

prettyString :: String -> Doc
   -- XXX should handle the escaping properly
-- prettyString str = text (show str)
prettyString str = text str

instance Pretty (Module a) where
   pretty (Module stmts) = vcat $ map pretty stmts

instance Pretty (Ident a) where
   pretty name@(Ident {}) = text $ ident_string name

prettyDottedName :: DottedName a -> Doc
prettyDottedName [] = empty
prettyDottedName [name] = pretty name
prettyDottedName (name:rest@(_:_))
   = pretty name <> dot <> prettyDottedName rest

instance Pretty (ImportItem a) where
   pretty (ImportItem {import_item_name = name, import_as_name = asName})
      = prettyDottedName name <+> (maybe empty (\n -> text "as" <+> pretty n) asName)

instance Pretty (FromItem a) where
   pretty (FromItem { from_item_name = name, from_as_name = asName })
      = pretty name <+> (maybe empty (\n -> text "as" <+> pretty n) asName)

instance Pretty (FromItems a) where
   pretty ImportEverything {} = char '*'
   pretty (FromItems { from_items_items = [item] }) = pretty item
   pretty (FromItems { from_items_items = items }) = parens (commaList items)

instance Pretty (ImportRelative a) where
   pretty (ImportRelative { import_relative_dots = dots, import_relative_module = mod })
      = case mod of
           Nothing -> dotDoc
           Just name -> dotDoc <> prettyDottedName name
      where
      dotDoc = text (replicate dots '.')

prettySuite :: [Statement a] -> Doc
prettySuite stmts = vcat $ map pretty stmts

optionalKeywordSuite :: String -> [Statement a] -> Doc
optionalKeywordSuite _ [] = empty
optionalKeywordSuite keyword stmts = text keyword <> colon $+$ indent (prettySuite stmts)

prettyParenList :: Pretty a => [a] -> Doc
prettyParenList = parens . commaList

prettyOptionalList :: Pretty a => [a] -> Doc
prettyOptionalList [] = empty
prettyOptionalList list = parens $ commaList list

prettyGuards :: [(Expr a, Suite a)] -> Doc
prettyGuards [] = empty
prettyGuards ((cond,body):guards)
   = text "elif" <+> pretty cond <> colon $+$ indent (prettySuite body) $+$
     prettyGuards guards

instance Pretty (Statement a) where
   -- pretty :: Statement -> Doc 
   pretty (Import { import_items = items}) = text "import" <+> commaList items
   pretty stmt@(FromImport {})
      = text "from" <+> pretty (from_module stmt) <+> text "import" <+> pretty (from_items stmt)
   pretty stmt@(While {})
      = text "while" <+> pretty (while_cond stmt) <> colon $+$
        indent (prettySuite (while_body stmt)) $+$ optionalKeywordSuite "else" (while_else stmt)
   pretty stmt@(For {})
      = text "for" <+> commaList (for_targets stmt) <+> text "in" <+> pretty (for_generator stmt) <> colon $+$
        indent (prettySuite (for_body stmt)) $+$ optionalKeywordSuite "else" (for_else stmt)
   pretty (AsyncFor { for_stmt = fs }) = zeroWidthText "async " <> pretty fs
   pretty stmt@(Fun {})
      = text "def" <+> pretty (fun_name stmt) <> parens (commaList (fun_args stmt)) <+>
        perhaps (fun_result_annotation stmt) (text "->") <+>
        pretty (fun_result_annotation stmt) <> colon $+$ indent (prettySuite (fun_body stmt))
   pretty (AsyncFun { fun_def = fd }) = zeroWidthText "async " <+> pretty fd
   pretty stmt@(Class {})
      = text "class" <+> pretty (class_name stmt) <> prettyOptionalList (class_args stmt) <>
        colon $+$ indent (prettySuite (class_body stmt))
   pretty stmt@(Conditional { cond_guards = guards, cond_else = optionalElse })
      = case guards of
           (cond,body):xs ->
              text "if" <+> pretty cond <> colon $+$ indent (prettySuite body) $+$
              prettyGuards xs $+$
              optionalKeywordSuite "else" optionalElse
           [] -> error "Attempt to pretty print conditional statement with empty guards"
   -- XXX is the assign_to always a singleton?
   pretty (Assign { assign_to = pattern, assign_expr = e })
      = equalsList pattern <+> equals <+> pretty e
   pretty (AugmentedAssign { aug_assign_to = to_expr, aug_assign_op = op, aug_assign_expr = e})
      = pretty to_expr <+> pretty op <+> pretty e
   pretty (AnnotatedAssign { ann_assign_annotation = ann_annotate, ann_assign_to = ann_to, ann_assign_expr = ann_expr})
      = pretty ann_to <+> text ":" <+> pretty ann_annotate <+> fromMaybe empty (((text "=" <+>) . pretty) <$> ann_expr)
   pretty (Decorated { decorated_decorators = decs, decorated_def = stmt})
      = vcat (map pretty decs) $+$ pretty stmt
   pretty (Return { return_expr = e }) = text "return" <+> pretty e
   pretty (Try { try_body = body, try_excepts = handlers, try_else = optionalElse, try_finally = finally})
      = text "try" <> colon $+$ indent (prettySuite body) $+$
        prettyHandlers handlers $+$ optionalKeywordSuite "else" optionalElse $+$
        optionalKeywordSuite "finally" finally
   pretty (Raise { raise_expr = e })
      = text "raise" <+> pretty e
   pretty (With { with_context = context, with_body = body })
      = text "with" <+> hcat (punctuate comma (map prettyWithContext context)) <+> colon $+$
        indent (prettySuite body)
   pretty (AsyncWith { with_stmt = ws }) = zeroWidthText "async " <+> pretty ws
   pretty Pass {} = text "pass"
   pretty Break {} = text "break"
   pretty Continue {} = text "continue"
   pretty (Delete { del_exprs = es }) = text "del" <+> commaList es
   pretty (StmtExpr { stmt_expr = e }) = pretty e
   pretty (Global { global_vars = idents }) = text "global" <+> commaList idents
   pretty (NonLocal { nonLocal_vars = idents }) = text "nonlocal" <+> commaList idents
   pretty (Assert { assert_exprs = es }) = text "assert" <+> commaList es
   pretty (Print { print_chevron = have_chevron, print_exprs = es, print_trailing_comma = trail_comma }) =
      text "print" <> (if have_chevron then text " >>" else empty) <+>
      hcat (punctuate comma (map pretty es)) <>
      if trail_comma then comma else empty
   pretty (Exec { exec_expr = e, exec_globals_locals = gls }) =
      text "exec" <+> pretty e <+>
      maybe empty (\ (globals, next) -> text "in" <+> pretty globals <+>
      maybe empty (\locals -> comma <+> pretty locals) next) gls

prettyWithContext :: (Expr a, Maybe (Expr a)) -> Doc
prettyWithContext (e, Nothing) = pretty e
prettyWithContext (e, Just as) = pretty e <+> text "as" <+> pretty as

prettyHandlers :: [Handler a] -> Doc
prettyHandlers = foldr (\next rec -> pretty next $+$ rec) empty


instance Pretty (Handler a) where
   pretty (Handler { handler_clause = exceptClause, handler_suite = suite })
      = pretty exceptClause <> colon $+$ indent (prettySuite suite)

instance Pretty (ExceptClause a) where
   pretty (ExceptClause { except_clause = Nothing }) = text "except"
   pretty (ExceptClause { except_clause = Just (e, target)})
      = text "except" <+> pretty e <+> maybe empty (\t -> text "as" <+> pretty t) target

instance Pretty (RaiseExpr a) where
   pretty (RaiseV3 e) =
      maybe empty (\ (x, fromE) -> pretty x <+> (maybe empty (\f -> text "from" <+> pretty f) fromE)) e
   pretty (RaiseV2 exp) =
      maybe empty (\ (e1, next1) -> pretty e1 <>
      maybe empty (\ (e2, next2) -> comma <+> pretty e2 <>
      maybe empty (\ e3 -> comma <+> pretty e3) next2) next1) exp

instance Pretty (Decorator a) where
   pretty (Decorator { decorator_name = name, decorator_args = args })
      = char '@' <> prettyDottedName name <+> prettyOptionalList args

instance Pretty (Parameter a) where
   pretty (Param { param_name = ident, param_py_annotation = annot, param_default = def })
      = pretty ident <> (maybe empty (\e -> colon <> pretty e <> space) annot) <>
        maybe empty (\e -> equals <> pretty e) def
   pretty (VarArgsPos { param_name = ident, param_py_annotation = annot})
      = char '*' <> pretty ident <> (maybe empty (\e -> colon <> pretty e) annot)
   pretty (VarArgsKeyword { param_name = ident, param_py_annotation = annot })
      = text "**" <> pretty ident <> (maybe empty (\e -> colon <> pretty e) annot)
   pretty EndPositional {} = char '*'
   pretty (UnPackTuple { param_unpack_tuple = tuple, param_default = def })
      = pretty tuple <> maybe empty (\e -> equals <> pretty e) def

instance Pretty (ParamTuple a) where
   pretty (ParamTupleName { param_tuple_name = name }) = pretty name
   pretty (ParamTuple { param_tuple = tuple }) = prettyParenList tuple

instance Pretty (Argument a) where
   pretty (ArgExpr { arg_expr = e }) = pretty e
   pretty (ArgVarArgsPos { arg_expr = e}) = char '*' <> pretty e
   pretty (ArgVarArgsKeyword { arg_expr = e }) = text "**" <> pretty e
   pretty (ArgKeyword { arg_keyword = ident, arg_expr = e })
      = pretty ident <> equals <> pretty e

instance Pretty (Comprehension a) where
   pretty (Comprehension { comprehension_expr = e, comprehension_for = for })
      = pretty e <+> pretty for

instance Pretty (ComprehensionExpr a) where
   pretty (ComprehensionExpr e) = pretty e
   pretty (ComprehensionDict d) = pretty d

instance Pretty (CompFor a) where
   pretty (CompFor { comp_for_async = ca, comp_for_exprs = es, comp_in_expr = e, comp_for_iter = iter })
      = (text $ if ca then "async for" else "for") <+> commaList es
      <+> text "in" <+> pretty e <+> pretty iter

instance Pretty (CompIf a) where
   pretty (CompIf { comp_if = e, comp_if_iter = iter })
      = text "if" <+> pretty e <+> pretty iter

instance Pretty (CompIter a) where
   pretty (IterFor { comp_iter_for = compFor }) = pretty compFor
   pretty (IterIf { comp_iter_if = compIf }) = pretty compIf

instance Pretty (Expr a) where
   pretty (Var { var_ident = i }) = pretty i
   pretty (Int { expr_literal = str }) = text str
   pretty (LongInt { expr_literal = str }) = text str
   pretty (Float { expr_literal = str }) = text str
   pretty (Imaginary { expr_literal = str }) = text str
   pretty (Bool { bool_value = b}) = pretty b
   pretty None {} = text "None"
   pretty Ellipsis {} = text "..."
   pretty (ByteStrings { byte_string_strings = bs }) = hcat (map pretty bs)
   pretty (Strings { strings_strings = ss }) = hcat (map prettyString ss)
   pretty (UnicodeStrings { unicodestrings_strings = ss }) = hcat (map prettyString ss)
   pretty (Call { call_fun = f, call_args = args }) = pretty f <> prettyParenList args
   pretty (Subscript { subscriptee = e, subscript_expr = sub })
      = pretty e <> brackets (pretty sub)
   pretty (SlicedExpr { slicee = e, slices = ss })
      = pretty e <> brackets (commaList ss)
   pretty (CondExpr { ce_true_branch = trueBranch, ce_condition = cond, ce_false_branch = falseBranch })
      = pretty trueBranch <+> text "if" <+> pretty cond <+> text "else" <+> pretty falseBranch
   pretty (BinaryOp { operator = op, left_op_arg = left, right_op_arg = right })
      = pretty left <+> pretty op <+> pretty right
   pretty (UnaryOp { operator = op, op_arg = e }) = pretty op <+> pretty e
   pretty (Dot { dot_expr = e, dot_attribute = a }) =
      pretty e <> dot <> pretty a
   pretty (Lambda { lambda_args = args, lambda_body = body })
      = text "lambda" <+> commaList args <> colon <+> pretty body
   pretty (Tuple { tuple_exprs = es }) =
      case es of
         [] -> text "()"
         [e] -> pretty e <> comma
         _ -> commaList es
   pretty (Yield { yield_arg = arg })
      = text "yield" <+> pretty arg
   pretty (Generator { gen_comprehension = gc }) = parens $ pretty gc
   pretty (Await { await_expr = ae }) = text "await" <+> pretty ae
   pretty (ListComp { list_comprehension = lc }) = brackets $ pretty lc
   pretty (List { list_exprs = es }) = brackets (commaList es)
   pretty (Dictionary { dict_mappings = mappings })
      = braces (hsep (punctuate comma $ map pretty mappings))
   pretty (DictComp { dict_comprehension = dc }) = braces $ pretty dc
   pretty (Set { set_exprs = es }) = braces $ commaList es
   pretty (SetComp { set_comprehension = sc }) = braces $ pretty sc
   pretty (Paren { paren_expr = e }) = parens $ pretty e
   pretty (StringConversion { backquoted_expr = e }) = char '`' <> pretty e <> char '`'
   pretty (Starred { starred_expr = e }) = char '*' <> pretty e

instance Pretty (YieldArg a) where
   pretty (YieldFrom e _annot) = text "from" <+> pretty e
   pretty (YieldExpr e) = pretty e

instance Pretty (DictKeyDatumList a) where
   pretty (DictMappingPair key val) = pretty key <> colon <+> pretty val
   pretty (DictUnpacking expr) = text "**" <> pretty expr

instance Pretty (Slice a) where
   pretty (SliceProper { slice_lower = lower, slice_upper = upper, slice_stride = stride })
      = pretty lower <> colon <> pretty upper <> (maybe empty (\s -> colon <> pretty s) stride)
   pretty (SliceExpr { slice_expr = e }) = pretty e
   pretty (SliceEllipsis {}) = text "..."

instance Pretty (Op a) where
   pretty (And {}) = text "and"
   pretty (Or {}) = text "or"
   pretty (Not {}) = text "not"
   pretty (Exponent {}) = text "**"
   pretty (LessThan {}) = text "<"
   pretty (GreaterThan {}) = text ">"
   pretty (Equality {}) = text "=="
   pretty (GreaterThanEquals {}) = text ">="
   pretty (LessThanEquals {}) = text "<="
   pretty (NotEquals {}) = text "!="
   pretty (NotEqualsV2 {}) = text "<>"
   pretty (In {}) = text "in"
   pretty (Is {}) = text "is"
   pretty (IsNot {}) = text "is not"
   pretty (NotIn {}) = text "not in"
   pretty (BinaryOr {}) = text "|"
   pretty (Xor {}) = text "^"
   pretty (BinaryAnd {}) = text "&"
   pretty (ShiftLeft {}) = text "<<"
   pretty (ShiftRight {}) = text ">>"
   pretty (Multiply {}) = text "*"
   pretty (Plus {}) = text "+"
   pretty (Minus {}) = text "-"
   pretty (Divide {}) = text "/"
   pretty (FloorDivide {}) = text "//"
   pretty (MatrixMult {}) = text "@"
   pretty (Invert {}) = text "~"
   pretty (Modulo {}) = text "%"

instance Pretty (AssignOp a) where
   pretty (PlusAssign {}) = text "+="
   pretty (MinusAssign {}) = text "-="
   pretty (MultAssign {}) = text "*="
   pretty (DivAssign {}) = text "/="
   pretty (ModAssign {}) = text "%="
   pretty (PowAssign {}) = text "**="
   pretty (BinAndAssign {}) = text "&="
   pretty (BinOrAssign {}) = text "|="
   pretty (BinXorAssign {}) = text "^="
   pretty (LeftShiftAssign {}) = text "<<="
   pretty (RightShiftAssign {}) = text ">>="
   pretty (FloorDivAssign {}) = text "//="
   pretty (MatrixMultAssign {}) = text "@="