{-# language GeneralizedNewtypeDeriving #-}
{-# language FlexibleInstances, MultiParamTypeClasses #-}
{-# language OverloadedStrings #-}

{-|
Module      : Language.Python.Internal.Render
Copyright   : (C) CSIRO 2017-2019
License     : BSD3
Maintainer  : Isaac Elliott <isaace71295@gmail.com>
Stability   : experimental
Portability : non-portable
-}

module Language.Python.Internal.Render
  ( -- * Common Functions
    showModule, showStatement, showExpr
    -- * Rendering
  , RenderOutput, showRenderOutput, singleton
  , renderModule, renderStatement, renderExpr
    -- * Miscellany
  , showQuoteType, showStringPrefix, showBytesPrefix, showToken, showTokens
  , expandIndents, whitespaceTokens, commentTokens
  , parens, braces, brackets
  , renderWhitespace, renderCommaSep, renderCommaSep1, renderCommaSep1'
  , renderIdent, renderComment, renderModuleName, renderDot, renderRelativeModuleName
  , renderImportAs, renderImportTargets, renderSimpleStatement, renderCompoundStatement
  , renderBlock, renderIndent, renderIndents, renderExceptAs, renderArg, renderParam
  , renderParams, renderCompFor, renderCompIf, renderComprehension, renderBinOp, renderUnOp
  , renderSubscript, renderPyChars, escapeChars, intToHex
  )
where

import Control.Lens.Cons (_init, _last)
import Control.Lens.Fold ((^..), folded, traverseOf_)
import Control.Lens.Getter ((^.))
import Control.Lens.Review ((#))
import Control.Lens.Setter ((.~))
import Control.Monad.Writer.Strict (Writer, execWriter, writer)
import Control.Monad.Reader (ReaderT, runReaderT, local, ask)
import Data.Bifoldable (bitraverse_, bitraverse_)
import Data.Char (ord)
import Data.Digit.Char (charHeXaDeCiMaL, charOctal)
import Data.Digit.Hexadecimal.MixedCase (HeXDigit(..))
import Data.DList (DList)
import Data.Function ((&))
import Data.Foldable (toList, traverse_)
import Data.Maybe (isNothing)
import Data.Semigroup (Semigroup(..))
import Data.Text (Text)

import qualified Data.DList as DList
import qualified Data.List.NonEmpty as NonEmpty
import qualified Data.Text as Text
import qualified Data.Text.Lazy as Lazy
import qualified Data.Text.Lazy.Builder as Builder

import Language.Python.Internal.Render.Correction
import Language.Python.Internal.Token (PyToken(..))
import Language.Python.Syntax.AugAssign
import Language.Python.Syntax.CommaSep
import Language.Python.Syntax.Comment
import Language.Python.Syntax.Expr
import Language.Python.Syntax.Ident
import Language.Python.Syntax.Import
import Language.Python.Syntax.Module
import Language.Python.Syntax.ModuleNames
import Language.Python.Syntax.Numbers
import Language.Python.Syntax.Operator.Binary
import Language.Python.Syntax.Operator.Unary
import Language.Python.Syntax.Punctuation
import Language.Python.Syntax.Statement
import Language.Python.Syntax.Strings
import Language.Python.Syntax.Whitespace

-- | A 'RenderOutput' is an intermediate form used during rendering
-- with efficient concatenation
newtype RenderOutput a
  = RenderOutput
  { unRenderOutput
    :: ReaderT
         -- Is the thing we're rendering followed by an optional
         -- newline? (as opposed to mandatory newline)
         --
         -- This is because the AST may be missing critical newlines
         -- and we supply them during rendering
         Bool
         (Writer (DList (PyToken ())))
         a
  } deriving (Functor, Applicative, Monad)

-- | Treats the input as a terminating statement (does not cause additional newlines to
-- be inserted)
final :: RenderOutput a -> RenderOutput a
final = id

-- | Treats the input as a non-terminating statement (causes additional newlines to be
-- inserted)
notFinal :: RenderOutput a -> RenderOutput a
notFinal (RenderOutput a) = RenderOutput $ local (const False) a

-- | Are we inside a terminating or non-terminating context?
isFinal :: RenderOutput Bool
isFinal = RenderOutput ask

-- | Render a single token as a 'RenderOutput'
singleton :: PyToken () -> RenderOutput ()
singleton a = RenderOutput $ writer ((), DList.singleton a)

-- | Run a 'RenderOutput' to produce a final 'Text'.
--
-- These 'Text's should then not be appended any more. All appending should
-- be done during the 'RenderOutput' phase.
showRenderOutput :: RenderOutput a -> Text
showRenderOutput =
  Lazy.toStrict .
  Builder.toLazyText .
  foldMap (Builder.fromText . showToken) .
  correctSpaces showToken .
  correctNewlines .
  DList.toList .
  execWriter .
  flip runReaderT True .
  unRenderOutput

renderComment :: Comment a -> RenderOutput ()
renderComment = traverse_ singleton . commentTokens

commentTokens :: Comment a -> [PyToken ()]
commentTokens c = [TkComment $ () <$ c]

showComment :: Comment a -> Text
showComment (MkComment _ s) = Text.pack $ "#" <> s

between :: RenderOutput l -> RenderOutput r -> RenderOutput a -> RenderOutput a
between l r m = l *> m <* r

parens :: RenderOutput a -> RenderOutput a
parens = between (singleton $ TkLeftParen ()) (singleton $ TkRightParen ())

brackets :: RenderOutput a -> RenderOutput a
brackets = between (singleton $ TkLeftBracket ()) (singleton $ TkRightBracket ())

braces :: RenderOutput a -> RenderOutput a
braces = between (singleton $ TkLeftBrace ()) (singleton $ TkRightBrace ())

-- | Parenthesise a term, but put its trailing whitespace *outside* the parens
parensDistTWS
  :: HasTrailingWhitespace s
  => (s -> RenderOutput ())
  -> s -> RenderOutput ()
parensDistTWS f a = do
  parens $ f (a & trailingWhitespace .~ [])
  traverse_ renderWhitespace (a ^. trailingWhitespace)

parensTuple :: Expr v a -> RenderOutput ()
parensTuple e =
  case e of
    Tuple{} -> parensDistTWS renderExpr e
    _ -> renderExpr e

parensGenerator :: Expr v a -> RenderOutput ()
parensGenerator e =
  case e of
    Generator{} -> parensDistTWS renderExpr e
    _ -> renderExpr e

parensTupleGenerator :: Expr v a -> RenderOutput ()
parensTupleGenerator e =
  case e of
    Tuple{} -> parensDistTWS renderExpr e
    Generator{} -> parensDistTWS renderExpr e
    _ -> renderExpr e

escapeChars :: [(Char, Char)]
escapeChars =
  [ ('\\', '\\')
  , ('"', '"')
  , ('\a', 'a')
  , ('\b', 'b')
  , ('\f', 'f')
  , ('\n', 'n')
  , ('\r', 'r')
  , ('\t', 't')
  , ('\v', 'v')
  ]

intToHex :: Int -> Text
intToHex n = Text.pack $ go n []
  where
    go 0 = (++"0")
    go 1 = (++"1")
    go 2 = (++"2")
    go 3 = (++"3")
    go 4 = (++"4")
    go 5 = (++"5")
    go 6 = (++"6")
    go 7 = (++"7")
    go 8 = (++"8")
    go 9 = (++"9")
    go 10 = (++"A")
    go 11 = (++"B")
    go 12 = (++"C")
    go 13 = (++"D")
    go 14 = (++"E")
    go 15 = (++"F")
    go b = let (q, r) = quotRem b 16 in go r . go q

intToHexH :: Int -> [HeXDigit]
intToHexH n = go n []
  where
    go 0 = (++[HeXDigit0])
    go 1 = (++[HeXDigit1])
    go 2 = (++[HeXDigit2])
    go 3 = (++[HeXDigit3])
    go 4 = (++[HeXDigit4])
    go 5 = (++[HeXDigit5])
    go 6 = (++[HeXDigit6])
    go 7 = (++[HeXDigit7])
    go 8 = (++[HeXDigit8])
    go 9 = (++[HeXDigit9])
    go 10 = (++[HeXDigitA])
    go 11 = (++[HeXDigitB])
    go 12 = (++[HeXDigitC])
    go 13 = (++[HeXDigitD])
    go 14 = (++[HeXDigitE])
    go 15 = (++[HeXDigitF])
    go b = let (q, r) = quotRem b 16 in go r . go q

renderPyCharsWithCorrection
  :: (QuoteType -> StringType -> [PyChar] -> [PyChar])
  -> QuoteType
  -> StringType
  -> [PyChar] -> Text
renderPyCharsWithCorrection c qt st = Text.pack . go . c qt st
  where
    go s =
      case s of
        [] -> ""
        Char_newline : cs -> "\\newline" <> go cs
        Char_octal1 a : cs ->
          "\\" <>
          [charOctal # a] <>
          go cs
        Char_octal2 a b : cs ->
          "\\" <>
          [charOctal # a, charOctal # b] <>
          go cs
        Char_octal3 a b c : cs ->
          "\\" <>
          [charOctal # a, charOctal # b, charOctal # c] <>
          go cs
        Char_hex a b : cs ->
          "\\x" <> [charHeXaDeCiMaL # a, charHeXaDeCiMaL # b] <> go cs
        Char_uni16 a b c d : cs ->
          "\\u" <>
          [ charHeXaDeCiMaL # a
          , charHeXaDeCiMaL # b
          , charHeXaDeCiMaL # c
          , charHeXaDeCiMaL # d
          ] <>
          go cs
        Char_uni32 a b c d e f g h : cs ->
          "\\u" <>
          [ charHeXaDeCiMaL # a
          , charHeXaDeCiMaL # b
          , charHeXaDeCiMaL # c
          , charHeXaDeCiMaL # d
          , charHeXaDeCiMaL # e
          , charHeXaDeCiMaL # f
          , charHeXaDeCiMaL # g
          , charHeXaDeCiMaL # h
          ] <>
          go cs
        Char_esc_bslash : cs -> '\\' : '\\' : go cs
        Char_esc_singlequote : cs -> '\\' : '\'' : go cs
        Char_esc_doublequote : cs -> '\\' : '"' : go cs
        Char_esc_a : cs -> '\\' : 'a' : go cs
        Char_esc_b : cs -> '\\' : 'b' : go cs
        Char_esc_f : cs -> '\\' : 'f' : go cs
        Char_esc_n : cs -> '\\' : 'n' : go cs
        Char_esc_r : cs -> '\\' : 'r' : go cs
        Char_esc_t : cs -> '\\' : 't' : go cs
        Char_esc_v : cs -> '\\' : 'v' : go cs
        Char_lit c : cs ->
          case st of
            LongString -> c : go cs
            ShortString ->
              case c of
                '\r' -> go $ Char_esc_r : cs
                '\n' -> go $ Char_esc_n : cs
                _ -> c : go cs

renderPyChars :: QuoteType -> StringType -> [PyChar] -> Text
renderPyChars =
  renderPyCharsWithCorrection $
  \qt st ->
    case st of
      LongString ->
        correctBackslashes . correctBackslashEscapes .
        correctInitialFinalQuotesLong qt
      ShortString ->
        correctBackslashes . correctBackslashEscapes .
        correctQuotes qt

renderRawPyChars :: QuoteType -> StringType -> [PyChar] -> Text
renderRawPyChars =
  renderPyCharsWithCorrection $
  \qt st ->
    case st of
      LongString ->
        correctInitialFinalQuotesLongRaw qt .
        correctBackslashEscapesRaw .
        correctBackslashesRaw
      ShortString ->
        correctBackslashEscapesRaw . correctBackslashesRaw .
        correctQuotesRaw qt

renderPyCharsBytesWithCorrection
  :: (QuoteType -> StringType -> [PyChar] -> [PyChar])
  -> QuoteType
  -> StringType
  -> [PyChar] -> Text
renderPyCharsBytesWithCorrection c qt st = Text.pack . go . c qt st
  where
    go s =
      case s of
        [] -> ""
        Char_newline : cs -> "\\newline" <> go cs
        Char_octal1 a  : cs ->
          "\\" <>
          [charOctal # a] <>
          go cs
        Char_octal2 a b : cs ->
          "\\" <>
          [charOctal # a, charOctal # b] <>
          go cs
        Char_octal3 a b c : cs ->
          "\\" <>
          [charOctal # a, charOctal # b, charOctal # c] <>
          go cs
        Char_hex a b : cs ->
          "\\x" <> [charHeXaDeCiMaL # a, charHeXaDeCiMaL # b] <> go cs
        Char_uni16 a b c d : cs ->
          "\\u" <>
          [ charHeXaDeCiMaL # a
          , charHeXaDeCiMaL # b
          , charHeXaDeCiMaL # c
          , charHeXaDeCiMaL # d
          ] <>
          go cs
        Char_uni32 a b c d e f g h : cs ->
          "\\u" <>
          [ charHeXaDeCiMaL # a
          , charHeXaDeCiMaL # b
          , charHeXaDeCiMaL # c
          , charHeXaDeCiMaL # d
          , charHeXaDeCiMaL # e
          , charHeXaDeCiMaL # f
          , charHeXaDeCiMaL # g
          , charHeXaDeCiMaL # h
          ] <>
          go cs
        Char_esc_bslash : cs -> '\\' : '\\' : go cs
        Char_esc_singlequote : cs -> '\\' : '\'' : go cs
        Char_esc_doublequote : cs -> '\\' : '"' : go cs
        Char_esc_a : cs -> '\\' : 'a' : go cs
        Char_esc_b : cs -> '\\' : 'b' : go cs
        Char_esc_f : cs -> '\\' : 'f' : go cs
        Char_esc_n : cs -> '\\' : 'n' : go cs
        Char_esc_r : cs -> '\\' : 'r' : go cs
        Char_esc_t : cs -> '\\' : 't' : go cs
        Char_esc_v : cs -> '\\' : 'v' : go cs
        Char_lit c : cs
          | o <- ord c, o > 127 ->
            let
              h = intToHexH o
            in
            case replicate (8 - length h) HeXDigit0 <> h of
              [a, b, c, d, e, f, g, h] -> go $ Char_uni32 a b c d e f g h : cs
              _ -> error $ "character " <> show c <> " out of unicode range"
          | otherwise ->
              case st of
                LongString -> c : go cs
                ShortString ->
                  case c of
                    '\r' -> go $ Char_esc_r : cs
                    '\n' -> go $ Char_esc_n : cs
                    _ -> c : go cs

renderPyCharsBytes :: QuoteType -> StringType -> [PyChar] -> Text
renderPyCharsBytes =
  renderPyCharsBytesWithCorrection $
  \qt st ->
  case st of
    LongString ->
      correctBackslashes . correctBackslashEscapes . correctInitialFinalQuotesLong qt
    ShortString ->
      correctBackslashes . correctBackslashEscapes . correctQuotes qt

renderRawPyCharsBytes :: QuoteType -> StringType -> [PyChar] -> Text
renderRawPyCharsBytes =
  renderPyCharsBytesWithCorrection $
  \qt st ->
    case st of
      LongString ->
        correctInitialFinalQuotesLongRaw qt .
        correctBackslashEscapesRaw .
        correctBackslashesRaw
      ShortString ->
        correctBackslashEscapesRaw . correctBackslashesRaw .
        correctQuotesRaw qt

showTokens :: [PyToken a] -> Text
showTokens =
  Lazy.toStrict .
  Builder.toLazyText .
  foldMap (Builder.fromText . showToken . (() <$)) .
  (expandIndents =<<)

expandIndents :: PyToken a -> [PyToken ()]
expandIndents (TkIndent _ i) =
  (i ^.. indentsValue.folded.indentWhitespaces.folded) >>=
  whitespaceTokens
expandIndents (TkLevel _ i) =
  (i ^.. indentsValue.folded.indentWhitespaces.folded) >>=
  whitespaceTokens
expandIndents TkDedent{} = []
expandIndents a = pure $ () <$ a

showToken :: PyToken a -> Text
showToken t =
  case t of
    TkIndent{} -> error "trying to show indent token"
    TkLevel{} -> error "trying to show level token"
    TkDedent{} -> error "trying to show dedent token"
    TkIf{} -> "if"
    TkElse{} -> "else"
    TkElif{} -> "elif"
    TkWhile{} -> "while"
    TkAssert{} -> "assert"
    TkDef{} -> "def"
    TkReturn{} -> "return"
    TkPass{} -> "pass"
    TkBreak{} -> "break"
    TkContinue{} -> "continue"
    TkTrue{} -> "True"
    TkFalse{} -> "False"
    TkNone{} -> "None"
    TkEllipsis{} -> "..."
    TkOr{} -> "or"
    TkAnd{} -> "and"
    TkIs{} -> "is"
    TkNot{} -> "not"
    TkGlobal{} -> "global"
    TkNonlocal{} -> "nonlocal"
    TkDel{} -> "del"
    TkLambda{} -> "lambda"
    TkImport{} -> "import"
    TkFrom{} -> "from"
    TkAs{} -> "as"
    TkRaise{} -> "raise"
    TkTry{} -> "try"
    TkExcept{} -> "except"
    TkFinally{} -> "finally"
    TkClass{} -> "class"
    TkRightArrow{} -> "->"
    TkWith{} -> "with"
    TkFor{} -> "for"
    TkIn{} -> "in"
    TkYield{} -> "yield"
    TkInt i -> showIntLiteral i
    TkFloat i -> showFloatLiteral i
    TkImag i -> showImagLiteral i
    TkIdent s _ -> Text.pack s
    TkString sp st qt s _ ->
      let
        quote =
          Text.pack $
          (case st of; LongString -> replicate 3; ShortString -> pure) (showQuoteType qt)
      in
        foldMap showStringPrefix sp <>
        quote <>
        renderPyChars qt st s <>
        quote
    TkBytes sp st qt s _ ->
      let
        quote =
          Text.pack $
          (case st of; LongString -> replicate 3; ShortString -> pure) (showQuoteType qt)
      in
        showBytesPrefix sp <>
        quote <>
        renderPyCharsBytes qt st s <>
        quote
    TkRawString sp st qt s _ ->
      let
        quote =
          case st of
            LongString -> Text.pack . replicate 3 $ showQuoteType qt
            ShortString -> Text.singleton $ showQuoteType qt
      in
        showRawStringPrefix sp <>
        quote <>
        renderRawPyChars qt st s <>
        quote
    TkRawBytes sp st qt s _ ->
      let
        quote =
          case st of
            LongString -> Text.pack . replicate 3 $ showQuoteType qt
            ShortString -> Text.singleton $ showQuoteType qt
      in
        showRawBytesPrefix sp <>
        quote <>
        renderRawPyCharsBytes qt st s <>
        quote
    TkSpace{} -> " "
    TkTab{} -> "\t"
    TkNewline nl _ ->
      case nl of
        CR -> "\r"
        LF -> "\n"
        CRLF -> "\r\n"
    TkLeftBracket{} -> "["
    TkRightBracket{} -> "]"
    TkLeftParen{} -> "("
    TkRightParen{} -> ")"
    TkLeftBrace{} -> "{"
    TkRightBrace{} -> "}"
    TkLt{} -> "<"
    TkLte{} -> "<="
    TkEq{} -> "="
    TkDoubleEq{}-> "=="
    TkBangEq{}-> "!="
    TkGt{} -> ">"
    TkGte{} -> ">="
    TkContinued nl _ ->
      "\\" <>
      case nl of
        CR -> "\r"
        LF -> "\n"
        CRLF -> "\r\n"
    TkColon{} -> ":"
    TkSemicolon{} -> ";"
    TkComma{} -> ","
    TkDot{} -> "."
    TkPlus{} -> "+"
    TkMinus{} -> "-"
    TkTilde{} -> "~"
    TkComment c -> showComment c
    TkStar{} -> "*"
    TkDoubleStar{} -> "**"
    TkSlash{} -> "/"
    TkDoubleSlash{} -> "//"
    TkPercent{} -> "%"
    TkShiftLeft{} -> "<<"
    TkShiftRight{} -> ">>"
    TkPlusEq{} -> "+="
    TkMinusEq{} -> "-="
    TkStarEq{} -> "*="
    TkAtEq{} -> "@="
    TkAt{} -> "@"
    TkSlashEq{} -> "/="
    TkPercentEq{} -> "%="
    TkAmpersandEq{} -> "&="
    TkPipeEq{} -> "|="
    TkCaretEq{} -> "^="
    TkAmpersand{} -> "&"
    TkPipe{} -> "|"
    TkCaret{} -> "^"
    TkShiftLeftEq{} -> "<<="
    TkShiftRightEq{} -> ">>="
    TkDoubleStarEq{} -> "**="
    TkDoubleSlashEq{} -> "//="

whitespaceTokens :: Whitespace -> [PyToken ()]
whitespaceTokens Space = [TkSpace ()]
whitespaceTokens Tab = [TkTab ()]
whitespaceTokens (Continued nl ws) = TkContinued nl () : (ws >>= whitespaceTokens)
whitespaceTokens (Newline nl) = [TkNewline nl ()]
whitespaceTokens (Comment cmt) = commentTokens cmt

renderWhitespace :: Whitespace -> RenderOutput ()
renderWhitespace = traverse_ singleton . whitespaceTokens

renderNewline :: Newline -> PyToken ()
renderNewline nl = TkNewline nl ()

renderComma :: Comma -> RenderOutput ()
renderComma (MkComma ws) = do
  singleton $ TkComma ()
  traverse_ renderWhitespace ws

renderAt :: At -> RenderOutput ()
renderAt (MkAt ws) = do
  singleton $ TkAt ()
  traverse_ renderWhitespace ws

renderCommaSep :: (a -> RenderOutput ()) -> CommaSep a -> RenderOutput ()
renderCommaSep _ CommaSepNone = pure ()
renderCommaSep f (CommaSepOne a) = f a
renderCommaSep f (CommaSepMany a c cs) = do
  f a
  renderComma c
  renderCommaSep f cs

renderCommaSep1 :: (a -> RenderOutput ()) -> CommaSep1 a -> RenderOutput ()
renderCommaSep1 f (CommaSepOne1 a) = f a
renderCommaSep1 f (CommaSepMany1 a comma c) = do
  f a
  renderComma comma
  renderCommaSep1 f c

renderCommaSep1' :: (a -> RenderOutput ()) -> CommaSep1' a -> RenderOutput ()
renderCommaSep1' f (CommaSepOne1' a b) = do
  f a
  traverse_
    renderComma
    b
renderCommaSep1' f (CommaSepMany1' a comma c) = do
  f a
  renderComma comma
  renderCommaSep1' f c

renderIdent :: Ident v a -> RenderOutput ()
renderIdent (MkIdent _ a b) = do
  singleton $ TkIdent a ()
  traverse_ renderWhitespace b

parensTernaryLambda :: (Expr v a -> RenderOutput ()) -> Expr v a -> RenderOutput ()
parensTernaryLambda _ e@Ternary{} = parensDistTWS renderExpr e
parensTernaryLambda _ e@Lambda{} = parensDistTWS renderExpr e
parensTernaryLambda f e = f e

renderCompFor :: CompFor v a -> RenderOutput ()
renderCompFor (CompFor _ ws1 ex1 ws2 ex2) = do
  singleton $ TkFor ()
  traverse_ renderWhitespace ws1
  (case ex1 of
     Not{} -> parensDistTWS renderExpr ex1
     _ -> parensGenerator ex1)
  singleton $ TkIn ()
  traverse_ renderWhitespace ws2
  parensTernaryLambda parensTupleGenerator ex2

renderCompIf :: CompIf v a -> RenderOutput ()
renderCompIf (CompIf _ ws ex) = do
  singleton $ TkIf ()
  traverse_ renderWhitespace ws
  parensTernaryLambda parensTupleGenerator ex

renderComprehension
  :: (e v a -> RenderOutput ())
  -> Comprehension e v a
  -> RenderOutput ()
renderComprehension f (Comprehension _ expr cf cs) = do
  f expr
  renderCompFor cf
  traverse_ (bitraverse_ renderCompFor renderCompIf) cs

renderDictItem :: DictItem v a -> RenderOutput ()
renderDictItem (DictItem _ a b c) = do
  parensTupleGenerator a
  renderColon b
  parensTupleGenerator c
renderDictItem (DictUnpack _ a b) = do
  singleton $ TkDoubleStar ()
  traverse_ renderWhitespace a
  case b of
    BinOp _ _ BoolAnd{} _ -> parensDistTWS renderExpr b
    BinOp _ _ BoolOr{} _ -> parensDistTWS renderExpr b
    BinOp _ _ op _ | isComparison op -> parensDistTWS renderExpr b
    Not{} -> parensDistTWS renderExpr b
    _ -> parensTernaryLambda parensTupleGenerator b

renderStringLiteral :: StringLiteral a -> RenderOutput ()
renderStringLiteral (StringLiteral _ a b c d e) = do
  singleton $ TkString a b c d ()
  traverse_ renderWhitespace e
renderStringLiteral (BytesLiteral _ a b c d e) = do
  singleton $ TkBytes a b c d ()
  traverse_ renderWhitespace e
renderStringLiteral (RawStringLiteral _ a b c d e) = do
  singleton $ TkRawString a b c d ()
  traverse_ renderWhitespace e
renderStringLiteral (RawBytesLiteral _ a b c d e) = do
  singleton $ TkRawBytes a b c d ()
  traverse_ renderWhitespace e

renderSubscript :: Subscript v a -> RenderOutput ()
renderSubscript (SubscriptExpr a) =
  case a of
    Await{} -> parensDistTWS renderExpr a
    _ -> parensTupleGenerator a
renderSubscript (SubscriptSlice a b c d) = do
  traverse_ parensTupleGenerator a
  renderColon b
  traverse_ parensTupleGenerator c
  traverse_
    (bitraverse_
      renderColon
      (traverse_ parensTupleGenerator))
    d

renderYield :: (Expr v a -> RenderOutput ()) -> Expr v a -> RenderOutput ()
renderYield _ (Yield _ a b) = do
  singleton $ TkYield ()
  traverse_ renderWhitespace a
  renderCommaSep parensTupleGenerator b
renderYield _ (YieldFrom _ a b c) = do
  singleton $ TkYield ()
  traverse_ renderWhitespace a
  singleton $ TkFrom ()
  traverse_ renderWhitespace b
  parensTupleGenerator c
renderYield re e = re e

renderUnpackTarget :: Expr v a -> RenderOutput ()
renderUnpackTarget e =
  case e of
    BinOp _ _ BoolAnd{} _ -> parensDistTWS renderExpr e
    BinOp _ _ BoolOr{} _ -> parensDistTWS renderExpr e
    BinOp _ _ op _ | isComparison op -> parensDistTWS renderExpr e
    Not{} -> parensDistTWS renderExpr e
    _ -> parensTernaryLambda parensTupleGenerator e

renderNestedParens
  :: RenderOutput ()
  -> [([Whitespace], [Whitespace])]
  -> RenderOutput ()
renderNestedParens =
  foldr
    (\(ws1, ws2) y -> do
        singleton $ TkLeftParen ()
        traverse_ renderWhitespace ws1
        y
        singleton $ TkRightParen ()
        traverse_ renderWhitespace ws2)

renderTupleItems
  :: CommaSep1' (TupleItem v a)
  -> RenderOutput ()
renderTupleItems (CommaSepOne1' a Nothing) =
  case a of
    TupleItem _ b -> parensTupleGenerator b
    TupleUnpack _ b c d ->
      renderNestedParens
        (do
            singleton $ TkStar ()
            traverse_ renderWhitespace c
            renderUnpackTarget d)
        b
renderTupleItems (CommaSepOne1' a (Just comma)) = do
  (case a of
     TupleItem _ b -> parensTupleGenerator b
     TupleUnpack _ [] b c ->
       parens $ do
         singleton $ TkStar ()
         traverse_ renderWhitespace b
         renderUnpackTarget c
     TupleUnpack _ b c d ->
       renderNestedParens
         (do
             singleton $ TkStar ()
             traverse_ renderWhitespace c
             renderUnpackTarget d)
         b)
  renderComma comma
renderTupleItems (CommaSepMany1' a comma rest) = do
  (case a of
    TupleItem _ b -> parensTupleGenerator b
    TupleUnpack _ [] b c ->
      parens $ do
        singleton $ TkStar ()
        traverse_ renderWhitespace b
        renderUnpackTarget c
    TupleUnpack _ b c d ->
      renderNestedParens
        (do
            singleton $ TkStar ()
            traverse_ renderWhitespace c
            renderUnpackTarget d)
        b)
  renderComma comma
  renderTupleItems rest

renderSetItem :: SetItem v a -> RenderOutput ()
renderSetItem a =
  case a of
    SetItem _ b -> parensTupleGenerator b
    SetUnpack _ b c d ->
      renderNestedParens
        (do
            singleton $ TkStar ()
            traverse_ renderWhitespace c
            renderUnpackTarget d)
        b

renderSetItems :: CommaSep1' (SetItem v a) -> RenderOutput ()
renderSetItems (CommaSepOne1' a Nothing) =
  case a of
    SetItem _ b -> parensTupleGenerator b
    SetUnpack _ b c d ->
      renderNestedParens
        (do
            singleton $ TkStar ()
            traverse_ renderWhitespace c
            renderUnpackTarget d)
        b
renderSetItems (CommaSepOne1' a (Just comma)) = do
  (case a of
     SetItem _ b -> parensTupleGenerator b
     SetUnpack _ [] b c -> do
       singleton $ TkStar ()
       traverse_ renderWhitespace b
       renderUnpackTarget c
     SetUnpack _ b c d ->
       renderNestedParens
         (do
             singleton $ TkStar ()
             traverse_ renderWhitespace c
             renderUnpackTarget d)
         b)
  renderComma comma
renderSetItems (CommaSepMany1' a comma rest) = do
  (case a of
    SetItem _ b -> parensTupleGenerator b
    SetUnpack _ [] b c -> do
      singleton $ TkStar ()
      traverse_ renderWhitespace b
      renderUnpackTarget c
    SetUnpack _ b c d ->
      renderNestedParens
        (do
            singleton $ TkStar ()
            traverse_ renderWhitespace c
            renderUnpackTarget d)
        b)
  renderComma comma
  renderSetItems rest

renderListItems :: CommaSep1' (ListItem v a) -> RenderOutput ()
renderListItems (CommaSepOne1' a Nothing) =
  case a of
    ListItem _ b -> parensTupleGenerator b
    ListUnpack _ b c d ->
      renderNestedParens
        (do
            singleton $ TkStar ()
            traverse_ renderWhitespace c
            renderUnpackTarget d)
        b
renderListItems (CommaSepOne1' a (Just comma)) = do
  (case a of
     ListItem _ b -> parensTupleGenerator b
     ListUnpack _ [] b c -> do
       singleton $ TkStar ()
       traverse_ renderWhitespace b
       renderUnpackTarget c
     ListUnpack _ b c d ->
       renderNestedParens
         (do
             singleton $ TkStar ()
             traverse_ renderWhitespace c
             renderUnpackTarget d)
         b)
  renderComma comma
renderListItems (CommaSepMany1' a comma rest) = do
  (case a of
    ListItem _ b -> parensTupleGenerator b
    ListUnpack _ [] b c -> do
      singleton $ TkStar ()
      traverse_ renderWhitespace b
      renderUnpackTarget c
    ListUnpack _ b c d ->
      renderNestedParens
        (do
            singleton $ TkStar ()
            traverse_ renderWhitespace c
            renderUnpackTarget d)
        b)
  renderComma comma
  renderListItems rest

renderExpr :: Expr v a -> RenderOutput ()
renderExpr (Unit _ a b) = do
  singleton $ TkLeftParen ()
  traverse_ renderWhitespace a
  singleton $ TkRightParen ()
  traverse_ renderWhitespace b
renderExpr (Lambda _ a b c d) = do
  singleton $ TkLambda ()
  traverse_ renderWhitespace a
  renderParams b
  renderColon c
  parensTupleGenerator d
renderExpr e@Yield{} = parensDistTWS (renderYield parensTupleGenerator) e
renderExpr e@YieldFrom{} = parensDistTWS (renderYield parensTupleGenerator) e
renderExpr (Ternary _ a b c d e) = do
  (case a of
     Generator{} -> parensDistTWS renderExpr a
     _ -> parensTupleGenerator a)
  singleton $ TkIf ()
  traverse_ renderWhitespace b
  parensTernaryLambda parensTupleGenerator c
  singleton $ TkElse ()
  traverse_ renderWhitespace d
  parensTupleGenerator e
renderExpr (Subscript _ a b c d) = do
  (case a of
     BinOp{} -> parensDistTWS renderExpr a
     UnOp{} -> parensDistTWS renderExpr a
     Not{} -> parensDistTWS renderExpr a
     Ternary{} -> parensDistTWS renderExpr a
     Lambda{} -> parensDistTWS renderExpr a
     Await{} -> parensDistTWS renderExpr a
     _ -> parensTupleGenerator a)
  brackets $ do
    traverse_ renderWhitespace b
    renderCommaSep1' renderSubscript c
  traverse_ renderWhitespace d
renderExpr (Not _ ws e) = do
  singleton $ TkNot ()
  traverse_ renderWhitespace ws
  case e of
    BinOp _ _ BoolAnd{} _ -> parensDistTWS renderExpr e
    BinOp _ _ BoolOr{} _ -> parensDistTWS renderExpr e
    Ternary{} -> parensDistTWS renderExpr e
    Lambda{} -> parensDistTWS renderExpr e
    _ -> parensTupleGenerator e
renderExpr (Parens _ ws1 e ws2) = do
  parens $ do
    traverse_ renderWhitespace ws1
    renderYield renderExpr e
  traverse_ renderWhitespace ws2
renderExpr (Bool _ b ws) = do
  singleton $ if b then TkTrue () else TkFalse ()
  traverse_ renderWhitespace ws
renderExpr (UnOp _ op expr) = do
  renderUnOp op
  case expr of
    BinOp _ _ Exp{} _ -> parensTupleGenerator expr
    BinOp{} -> parensDistTWS renderExpr expr
    Deref _ Int{} _ _ -> parensDistTWS renderExpr expr
    Not{} -> parensDistTWS renderExpr expr
    Ternary{} -> parensDistTWS renderExpr expr
    Lambda{} -> parensDistTWS renderExpr expr
    _ -> parensTupleGenerator expr
renderExpr (String _ vs) =
  traverse_ renderStringLiteral $ correctAdjacentStrings vs
renderExpr (Int _ n ws) = do
  singleton $ TkInt (() <$ n)
  traverse_ renderWhitespace ws
renderExpr (Float _ n ws) = do
  singleton $ TkFloat (() <$ n)
  traverse_ renderWhitespace ws
renderExpr (Imag _ n ws) = do
  singleton $ TkImag (() <$ n)
  traverse_ renderWhitespace ws
renderExpr (Ident _ name) = renderIdent name
renderExpr (List _ ws1 exprs ws2) = do
  brackets $ do
    traverse_ renderWhitespace ws1
    traverse_ renderListItems exprs
  traverse_ renderWhitespace ws2
renderExpr (ListComp _ ws1 comp ws2) = do
  brackets $ do
    traverse_ renderWhitespace ws1
    renderComprehension
      (\e -> case e of
          Yield{} -> parensDistTWS renderExpr e
          YieldFrom{} -> parensDistTWS renderExpr e
          _ -> parensTupleGenerator e)
      comp
  traverse_ renderWhitespace ws2
renderExpr (Call _ expr ws args ws2) = do
  (case expr of
     UnOp{} -> parensDistTWS renderExpr expr
     BinOp{} -> parensDistTWS renderExpr expr
     Tuple{} -> parensDistTWS renderExpr expr
     Not{} -> parensDistTWS renderExpr expr
     Ternary{} -> parensDistTWS renderExpr expr
     Lambda{} -> parensDistTWS renderExpr expr
     _ -> parensGenerator expr)
  parens $ do
    traverse_ renderWhitespace ws
    traverse_ renderArgs args
  traverse_ renderWhitespace ws2
renderExpr (Deref _ expr ws name) = do
  (case expr of
     Int{} -> parensDistTWS renderExpr expr
     BinOp{} -> parensDistTWS renderExpr expr
     Tuple{} -> parensDistTWS renderExpr expr
     Not{} -> parensDistTWS renderExpr expr
     UnOp{} -> parensDistTWS renderExpr expr
     Ternary{} -> parensDistTWS renderExpr expr
     Lambda{} -> parensDistTWS renderExpr expr
     Await{} -> parensDistTWS renderExpr expr
     _ -> parensGenerator expr)
  singleton $ TkDot ()
  traverse_ renderWhitespace ws
  renderIdent name
renderExpr (None _ ws) = do
  singleton $ TkNone ()
  traverse_ renderWhitespace ws
renderExpr (Ellipsis _ ws) = do
  singleton $ TkEllipsis ()
  traverse_ renderWhitespace ws
renderExpr (BinOp _ e1 op e2) = do
  if shouldGroupLeft op e1
    then parensDistTWS renderExpr e1
    else parensTernaryLambda parensGenerator e1

  renderBinOp op

  if shouldGroupRight op e2
    then parensDistTWS renderExpr e2
    else parensTernaryLambda parensGenerator e2
renderExpr (Tuple _ a ws c) =
  renderTupleItems $
  case c of
    Nothing -> CommaSepOne1' a (Just ws)
    Just c' -> CommaSepMany1' a ws c'
renderExpr (DictComp _ ws1 comp ws2) = do
  braces $ do
    traverse_ renderWhitespace ws1
    renderComprehension renderDictItem comp
  traverse_ renderWhitespace ws2
renderExpr (Dict _ a b c) = do
  braces $ do
    traverse_ renderWhitespace a
    traverse_ (renderCommaSep1' renderDictItem) b
  traverse_ renderWhitespace c
renderExpr (SetComp _ ws1 comp ws2) = do
  braces $ do
    traverse_ renderWhitespace ws1
    renderComprehension renderSetItem comp
  traverse_ renderWhitespace ws2
renderExpr (Set _ a b c) = do
  braces $ do
    traverse_ renderWhitespace a
    renderSetItems b
  traverse_ renderWhitespace c
renderExpr (Generator _ a) =
  renderComprehension
    (\e -> case e of
        Yield{} -> parensDistTWS renderExpr e
        YieldFrom{} -> parensDistTWS renderExpr e
        _ -> parensTupleGenerator e)
    a
renderExpr (Await _ ws expr) = do
  singleton $ TkIdent "await" ()
  traverse_ renderWhitespace ws
  (case expr of
     UnOp{} -> parensDistTWS renderExpr expr
     BinOp{} -> parensDistTWS renderExpr expr
     Tuple{} -> parensDistTWS renderExpr expr
     Not{} -> parensDistTWS renderExpr expr
     Ternary{} -> parensDistTWS renderExpr expr
     Lambda{} -> parensDistTWS renderExpr expr
     Await{} -> parensDistTWS renderExpr expr
     _ -> parensGenerator expr)

renderModuleName :: ModuleName v a -> RenderOutput ()
renderModuleName (ModuleNameOne _ s) = renderIdent s
renderModuleName (ModuleNameMany _ n dot rest) = do
  renderIdent n
  renderDot dot
  renderModuleName rest

renderDot :: Dot -> RenderOutput ()
renderDot (MkDot ws) = do
  singleton $ TkDot ()
  traverse_ renderWhitespace ws

renderRelativeModuleName :: RelativeModuleName v a -> RenderOutput ()
renderRelativeModuleName (RelativeWithName _ ds mn) = do
  traverse_ renderDot ds
  renderModuleName mn
renderRelativeModuleName (Relative _ ds) =
  traverse_ renderDot ds

renderImportAs :: (e v a -> RenderOutput ()) -> ImportAs e v a -> RenderOutput ()
renderImportAs f (ImportAs _ ea m) = do
  f ea
  traverse_
    (\(a, b) -> do
        singleton $ TkAs ()
        traverse_ renderWhitespace a
        renderIdent b)
    m

renderImportTargets :: ImportTargets v a -> RenderOutput ()
renderImportTargets (ImportAll _ ws) = do
  singleton $ TkStar ()
  traverse_ renderWhitespace ws
renderImportTargets (ImportSome _ ts) =
  renderCommaSep1 (renderImportAs renderIdent) ts
renderImportTargets (ImportSomeParens _ ws1 ts ws2) = do
  parens $ do
    traverse_ renderWhitespace ws1
    renderCommaSep1' (renderImportAs renderIdent) ts
  traverse_ renderWhitespace ws2

renderAugAssign :: AugAssign a -> RenderOutput ()
renderAugAssign aa = do
  singleton $ case _augAssignType aa of
    PlusEq -> TkPlusEq ()
    MinusEq -> TkMinusEq ()
    StarEq -> TkStarEq ()
    AtEq -> TkAtEq ()
    SlashEq -> TkSlashEq ()
    PercentEq -> TkPercentEq ()
    AmpersandEq -> TkAmpersandEq ()
    PipeEq -> TkPipeEq ()
    CaretEq -> TkCaretEq ()
    ShiftLeftEq -> TkShiftLeftEq ()
    ShiftRightEq -> TkShiftRightEq ()
    DoubleStarEq -> TkDoubleStarEq ()
    DoubleSlashEq -> TkDoubleSlashEq ()
  traverse_ renderWhitespace (_augAssignWhitespace aa)

renderSimpleStatement :: SimpleStatement v a -> RenderOutput ()
renderSimpleStatement (Assert _ b c d) = do
  singleton $ TkAssert ()
  traverse_ renderWhitespace b
  parensTupleGenerator c
  traverse_
    (\(a, b) -> do
        renderComma a
        parensTupleGenerator b)
    d
renderSimpleStatement (Raise _ ws x) = do
  singleton $ TkRaise ()
  traverse_ renderWhitespace ws
  traverse_
    (\(b, c) -> do
       parensTupleGenerator b
       traverse_
         (\(d, e) -> do
            singleton $ TkFrom ()
            traverse_ renderWhitespace d
            parensTupleGenerator e)
         c)
    x
renderSimpleStatement (Return _ ws expr) = do
  singleton $ TkReturn ()
  traverse_ renderWhitespace ws
  traverse_ parensGenerator expr
renderSimpleStatement (Expr _ expr) = renderYield parensGenerator expr
renderSimpleStatement (Assign _ lvalue rvalues) = do
  renderExpr lvalue
  traverse_
    (\(ws2, rvalue) -> do
       renderEquals ws2
       renderYield parensGenerator rvalue)
    rvalues
renderSimpleStatement (AugAssign _ lvalue as rvalue) = do
  renderExpr lvalue
  renderAugAssign as
  parensTupleGenerator rvalue
renderSimpleStatement (Pass _ ws) = do
  singleton $ TkPass ()
  traverse_ renderWhitespace ws
renderSimpleStatement (Continue _ ws) = do
  singleton $ TkContinue ()
  traverse_ renderWhitespace ws
renderSimpleStatement (Break _ ws) = do
  singleton $ TkBreak ()
  traverse_ renderWhitespace ws
renderSimpleStatement (Global _ ws ids) = do
  singleton $ TkGlobal ()
  traverse_ renderWhitespace ws
  renderCommaSep1 renderIdent ids
renderSimpleStatement (Nonlocal _ ws ids) = do
  singleton $ TkNonlocal ()
  traverse_ renderWhitespace ws
  renderCommaSep1 renderIdent ids
renderSimpleStatement (Del _ ws vals) = do
  singleton $ TkDel ()
  traverse_ renderWhitespace ws
  renderCommaSep1'
    (\a -> case a of
        BinOp{} -> parensDistTWS renderExpr a
        Not{} -> parensDistTWS renderExpr a
        Ternary{} -> parensDistTWS renderExpr a
        Lambda{} -> parensDistTWS renderExpr a
        _ -> parensTupleGenerator a)
    vals
renderSimpleStatement (Import _ ws ns) = do
  singleton $ TkImport ()
  traverse_ renderWhitespace ws
  renderCommaSep1 (renderImportAs renderModuleName) ns
renderSimpleStatement (From _ ws1 name ws3 ns) = do
  singleton $ TkFrom ()
  traverse_ renderWhitespace ws1
  renderRelativeModuleName name
  singleton $ TkImport ()
  traverse_ renderWhitespace ws3
  renderImportTargets ns

renderBlank :: Blank a -> RenderOutput ()
renderBlank (Blank _ a b) = do
  traverse_ renderWhitespace a
  traverse_ renderComment b

renderBlock :: Block v a -> RenderOutput ()
renderBlock (Block a b c) = do
  traverse_ (bitraverse_ renderBlank (singleton . renderNewline)) a
  (if null c then final else notFinal) $ renderStatement b
  traverseOf_
    (_init.traverse)
    (bitraverse_
      (bitraverse_ renderBlank (singleton . renderNewline))
      (notFinal . renderStatement))
    c
  traverseOf_
    _last
    (bitraverse_
      (bitraverse_ renderBlank (singleton . renderNewline))
      (final . renderStatement))
    c

renderSemicolon :: Semicolon a -> RenderOutput ()
renderSemicolon (MkSemicolon _ ws) = do
  singleton $ TkSemicolon ()
  traverse_ renderWhitespace ws

renderEquals :: Equals -> RenderOutput ()
renderEquals (MkEquals ws) = do
  singleton $ TkEq ()
  traverse_ renderWhitespace ws

renderColon :: Colon -> RenderOutput ()
renderColon (MkColon ws) = do
  singleton $ TkColon ()
  traverse_ renderWhitespace ws

renderSuite
  :: Suite v a
  -> RenderOutput ()
renderSuite (SuiteMany _ a b c d) = do
  renderColon a
  traverse_ renderComment b
  singleton (renderNewline c)
  renderBlock d
renderSuite (SuiteOne _ a b) = do
  renderColon a
  fin <- isFinal
  renderSmallStatement $ correctTrailingNewline fin b

renderDecorator :: Decorator v a -> RenderOutput ()
renderDecorator (Decorator _ a b c d e f) = do
  renderIndents a
  renderAt b
  renderExpr c
  traverse_ renderComment d
  singleton (renderNewline e)
  traverse_ (bitraverse_ renderBlank (singleton . renderNewline)) f

renderCompoundStatement :: CompoundStatement v a -> RenderOutput ()
renderCompoundStatement (Fundef _ decos idnt asyncWs ws1 name ws2 params ws3 mty s) = do
  traverse_ renderDecorator decos
  renderIndents idnt
  traverse_
    (\ws -> do
        singleton $ TkIdent "async" ()
        traverse_ renderWhitespace ws)
    asyncWs
  singleton (TkDef ())
  traverse_ renderWhitespace ws1
  renderIdent name
  parens $ do
    traverse_ renderWhitespace ws2
    renderParams params
  traverse_ renderWhitespace ws3
  traverse_
    (\(ws, ty) -> do
        singleton $ TkRightArrow ()
        traverse_ renderWhitespace ws
        parensTupleGenerator ty)
    mty
  final $ renderSuite s
renderCompoundStatement (If _ idnt ws1 expr s elifs body') = do
  renderIndents idnt
  singleton $ TkIf ()
  traverse_ renderWhitespace ws1
  parensTupleGenerator expr
  notFinal $ renderSuite s
  traverseOf_
    (_init.traverse)
    (\(idnt, ws4, ex, s) -> do
        renderIndents idnt
        singleton $ TkElif ()
        traverse_ renderWhitespace ws4
        parensTupleGenerator ex
        notFinal $ renderSuite s)
    elifs
  traverseOf_
    _last
    (\(idnt, ws4, ex, s) -> do
        renderIndents idnt
        singleton $ TkElif ()
        traverse_ renderWhitespace ws4
        parensTupleGenerator ex
        (if isNothing body' then final else notFinal) $ renderSuite s)
    elifs
  traverse_
    (\(idnt, ws4, s) -> do
        renderIndents idnt
        singleton $ TkElse ()
        traverse_ renderWhitespace ws4
        final $ renderSuite s)
    body'
renderCompoundStatement (While _ idnt ws1 expr s els) = do
  renderIndents idnt
  singleton $ TkWhile ()
  traverse_ renderWhitespace ws1
  parensTupleGenerator expr
  (if isNothing els then final else notFinal) $ renderSuite s
  traverse_
    (\(idnt, ws4, s) -> do
        renderIndents idnt
        singleton $ TkElse ()
        traverse_ renderWhitespace ws4
        final $ renderSuite s)
    els
renderCompoundStatement (TryExcept _ idnt a s e f g) = do
  renderIndents idnt
  singleton $ TkTry ()
  traverse_ renderWhitespace a
  notFinal $ renderSuite s
  traverse_
    (\(idnt, ws1, eas, s) -> do
       renderIndents idnt
       singleton $ TkExcept ()
       traverse_ renderWhitespace ws1
       traverse_ renderExceptAs eas
       notFinal $ renderSuite s)
    (NonEmpty.init e)
  (case NonEmpty.last e of
     (idnt, ws1, eas, s) -> do
       renderIndents idnt
       singleton $ TkExcept ()
       traverse_ renderWhitespace ws1
       traverse_ renderExceptAs eas
       (if isNothing f && isNothing g then final else notFinal) $ renderSuite s)
  traverse_
    (\(idnt, ws1, s) -> do
       renderIndents idnt
       singleton $ TkElse ()
       traverse_ renderWhitespace ws1
       (if isNothing g then final else notFinal) $ renderSuite s)
    f
  traverse_
    (\(idnt, ws1, s) -> do
       renderIndents idnt
       singleton $ TkFinally ()
       traverse_ renderWhitespace ws1
       final $ renderSuite s)
    g
renderCompoundStatement (TryFinally _ idnt a s idnt2 e s') = do
  renderIndents idnt
  singleton $ TkTry ()
  traverse_ renderWhitespace a
  notFinal $ renderSuite s
  renderIndents idnt2
  singleton $ TkFinally ()
  traverse_ renderWhitespace e
  final $ renderSuite s'
renderCompoundStatement (For _ idnt asyncWs a b c d s h) = do
  renderIndents idnt
  traverse_
    (\ws -> do
        singleton $ TkIdent "async" ()
        traverse_ renderWhitespace ws)
    asyncWs
  singleton $ TkFor ()
  traverse_ renderWhitespace a
  parensGenerator b
  singleton $ TkIn ()
  traverse_ renderWhitespace c
  renderCommaSep1' parensTupleGenerator d
  (if isNothing h then final else notFinal) $ renderSuite s
  traverse_
    (\(idnt, x, s) -> do
        renderIndents idnt
        singleton $ TkElse ()
        traverse_ renderWhitespace x
        final $ renderSuite s)
    h
renderCompoundStatement (ClassDef _ decos idnt a b c s) = do
  traverse_ renderDecorator decos
  renderIndents idnt
  singleton $ TkClass ()
  traverse_ renderWhitespace a
  renderIdent b
  traverse_
    (\(x, y, z) -> do
      parens $ do
        traverse_ renderWhitespace x
        traverse_ renderArgs y
      traverse_ renderWhitespace z)
    c
  final $ renderSuite s
renderCompoundStatement (With _ idnt asyncWs a b s) = do
  renderIndents idnt
  traverse_
    (\ws -> do
        singleton $ TkIdent "async" ()
        traverse_ renderWhitespace ws)
    asyncWs
  singleton $ TkWith ()
  traverse_ renderWhitespace a
  renderCommaSep1 renderWithItem b
  final $ renderSuite s

renderWithItem :: WithItem v a -> RenderOutput ()
renderWithItem (WithItem _ a b) = do
  parensTupleGenerator a
  traverse_
    (\(c, d) -> do
       singleton $ TkAs ()
       traverse_ renderWhitespace c
       parensTupleGenerator d)
    b

renderIndent :: Indent -> RenderOutput ()
renderIndent (MkIndent ws) = traverse_ renderWhitespace $ toList ws

renderSmallStatement :: SmallStatement v a -> RenderOutput ()
renderSmallStatement (MkSmallStatement s ss sc cmt nl) = do
  renderSimpleStatement s
  traverse_
    (\(b, c) -> do
       renderSemicolon b
       renderSimpleStatement c)
    ss
  traverse_ renderSemicolon sc
  traverse_ renderComment cmt
  traverse_ (singleton . renderNewline) nl

renderStatement :: Statement v a -> RenderOutput ()
renderStatement (CompoundStatement c) = renderCompoundStatement c
renderStatement (SmallStatement idnts s) = do
  renderIndents idnts
  fin <- isFinal
  renderSmallStatement $ correctTrailingNewline fin s

renderExceptAs :: ExceptAs v a -> RenderOutput ()
renderExceptAs (ExceptAs _ e f) = do
  parensTupleGenerator e
  traverse_
    (\(a, b) -> do
        singleton $ TkAs ()
        traverse_ renderWhitespace a
        renderIdent b)
    f

renderArgs :: CommaSep1' (Arg v a) -> RenderOutput ()
renderArgs (CommaSepOne1' a Nothing) = renderArg parensTuple a
renderArgs e = renderCommaSep1' (renderArg parensTupleGenerator) e

renderArg :: (Expr v a -> RenderOutput ()) -> Arg v a -> RenderOutput ()
renderArg re (PositionalArg _ expr) = re expr
renderArg _ (KeywordArg _ name ws2 expr) = do
  renderIdent name
  singleton $ TkEq ()
  traverse_ renderWhitespace ws2
  parensTupleGenerator expr
renderArg _ (StarArg _ ws expr) = do
  singleton $ TkStar ()
  traverse_ renderWhitespace ws
  parensTupleGenerator expr
renderArg _ (DoubleStarArg _ ws expr) = do
  singleton $ TkDoubleStar ()
  traverse_ renderWhitespace ws
  parensTupleGenerator expr

renderParams :: CommaSep (Param v a) -> RenderOutput ()
renderParams = renderCommaSep renderParam . correctParams

renderParam :: Param v a -> RenderOutput ()
renderParam (PositionalParam _ name mty) = do
  renderIdent name
  traverse_
    (\(c, ty) -> do
        renderColon c
        parensTupleGenerator ty)
    mty
renderParam (StarParam _ ws name mty) = do
  singleton $ TkStar ()
  traverse_ renderWhitespace ws
  renderIdent name
  traverse_
    (\(c, ty) -> do
        renderColon c
        parensTupleGenerator ty)
    mty
renderParam (UnnamedStarParam _ ws) = do
  singleton $ TkStar ()
  traverse_ renderWhitespace ws
renderParam (DoubleStarParam _ ws name mty) = do
  singleton $ TkDoubleStar ()
  traverse_ renderWhitespace ws
  renderIdent name
  traverse_
    (\(c, ty) -> do
        renderColon c
        parensTupleGenerator ty)
    mty
renderParam (KeywordParam _ name mty ws2 expr) = do
  renderIdent name
  traverse_
    (\(c, ty) -> do
        renderColon c
        parensTupleGenerator ty)
    mty
  singleton $ TkEq ()
  traverse_ renderWhitespace ws2
  parensTupleGenerator expr

renderUnOp :: UnOp a -> RenderOutput ()
renderUnOp (Negate _ ws) = do
  singleton $ TkMinus ()
  traverse_ renderWhitespace ws
renderUnOp (Positive _ ws) = do
  singleton $ TkPlus ()
  traverse_ renderWhitespace ws
renderUnOp (Complement _ ws) = do
  singleton $ TkTilde ()
  traverse_ renderWhitespace ws

renderBinOp :: BinOp a -> RenderOutput ()
renderBinOp (Is _ ws) = do
  singleton $ TkIs ()
  traverse_ renderWhitespace ws
renderBinOp (IsNot _ ws1 ws2) = do
  singleton $ TkIs ()
  traverse_ renderWhitespace ws1
  singleton $ TkNot ()
  traverse_ renderWhitespace ws2
renderBinOp (In _ ws) = do
  singleton $ TkIn ()
  traverse_ renderWhitespace ws
renderBinOp (NotIn _ ws1 ws2) = do
  singleton $ TkNot ()
  traverse_ renderWhitespace ws1
  singleton $ TkIn ()
  traverse_ renderWhitespace ws2
renderBinOp (Plus _ ws) = do
  singleton $ TkPlus ()
  traverse_ renderWhitespace ws
renderBinOp (Minus _ ws) = do
  singleton $ TkMinus ()
  traverse_ renderWhitespace ws
renderBinOp (Multiply _ ws) = do
  singleton $ TkStar ()
  traverse_ renderWhitespace ws
renderBinOp (At _ ws) = do
  singleton $ TkAt ()
  traverse_ renderWhitespace ws
renderBinOp (Divide _ ws) = do
  singleton $ TkSlash ()
  traverse_ renderWhitespace ws
renderBinOp (FloorDivide _ ws) = do
  singleton $ TkDoubleSlash ()
  traverse_ renderWhitespace ws
renderBinOp (Exp _ ws) = do
  singleton $ TkDoubleStar ()
  traverse_ renderWhitespace ws
renderBinOp (BoolAnd _ ws) = do
  singleton $ TkAnd ()
  traverse_ renderWhitespace ws
renderBinOp (BoolOr _ ws) = do
  singleton $ TkOr ()
  traverse_ renderWhitespace ws
renderBinOp (Eq _ ws) = do
  singleton $ TkDoubleEq ()
  traverse_ renderWhitespace ws
renderBinOp (Lt _ ws) = do
  singleton $ TkLt ()
  traverse_ renderWhitespace ws
renderBinOp (LtEq _ ws) = do
  singleton $ TkLte ()
  traverse_ renderWhitespace ws
renderBinOp (Gt _ ws) = do
  singleton $ TkGt ()
  traverse_ renderWhitespace ws
renderBinOp (GtEq _ ws) = do
  singleton $ TkGte ()
  traverse_ renderWhitespace ws
renderBinOp (NotEq _ ws) = do
  singleton $ TkBangEq ()
  traverse_ renderWhitespace ws
renderBinOp (Percent _ ws) = do
  singleton $ TkPercent ()
  traverse_ renderWhitespace ws
renderBinOp (BitOr _ ws) = do
  singleton $ TkPipe ()
  traverse_ renderWhitespace ws
renderBinOp (BitXor _ ws) = do
  singleton $ TkCaret ()
  traverse_ renderWhitespace ws
renderBinOp (BitAnd _ ws) = do
  singleton $ TkAmpersand ()
  traverse_ renderWhitespace ws
renderBinOp (ShiftLeft _ ws) = do
  singleton $ TkShiftLeft ()
  traverse_ renderWhitespace ws
renderBinOp (ShiftRight _ ws) = do
  singleton $ TkShiftRight ()
  traverse_ renderWhitespace ws

renderIndents :: Indents a -> RenderOutput ()
renderIndents (Indents is _) = traverse_ renderIndent is

renderModule :: Module v a -> RenderOutput ()
renderModule ModuleEmpty = pure ()
renderModule (ModuleBlankFinal a) = renderBlank a
renderModule (ModuleBlank a b c) = do
  renderBlank a
  singleton $ renderNewline b
  renderModule c
renderModule (ModuleStatement a b) = do
  renderStatement a
  renderModule b

-- | Render an entire Python module to 'Text'
showModule :: Module v a -> Text
showModule = showRenderOutput . renderModule

-- | Render a single Python statement to 'Text'
showStatement :: Statement v a -> Text
showStatement = showRenderOutput . renderStatement

-- | Render a single Python expression to 'Text'
showExpr :: Expr v a -> Text
showExpr = showRenderOutput . parensGenerator