packages feed

aihc-parser-1.0.0.2: src/Aihc/Parser/Shorthand.hs

{-# LANGUAGE OverloadedStrings #-}

-- |
--
-- Module      : Aihc.Parser.Shorthand
-- Description : Compact pretty-printing for debugging/inspection
--
-- This module provides a compact, human-readable representation of parsed
-- AST structures via the 'Shorthand' typeclass. Key features:
--
-- * Source spans are omitted to reduce noise
-- * Empty fields (Nothing, [], False, etc.) and field labels are omitted
-- * Remaining constructor/value tokens are a subsequence of the derived 'Show' output
-- * Output is on a single line by default
-- * Uses the prettyprinter library for consistent formatting
--
-- Example:
--
-- >>> shorthand $ snd $ parseModule defaultConfig "module Demo where x = 1"
-- Module {ModuleHead {"Demo"}, [DeclValue (PatternBind (PVar "x") (EInt 1 TInteger))]}
module Aihc.Parser.Shorthand
  ( Shorthand (..),
  )
where

import Aihc.Parser.Lex (LexToken (..), LexTokenKind (..))
import Aihc.Parser.Syntax
import Aihc.Parser.Types (ParseResult (..))
import Data.Text (Text)
import Prettyprinter
  ( Doc,
    Pretty (..),
    braces,
    brackets,
    comma,
    hsep,
    parens,
    punctuate,
    (<+>),
  )

-- $setup
-- >>> :set -XOverloadedStrings
-- >>> import Aihc.Parser

-- | Typeclass for compact, human-readable AST representations.
--
-- The 'shorthand' method produces a 'Doc' that can be rendered to text
-- or shown as a string. This is useful for debugging and golden tests.
--
-- Use 'show' on the result of 'shorthand' to get a 'String':
--
-- @
-- show (shorthand expr) :: String
-- @
class Shorthand a where
  shorthand :: a -> Doc ()

-- ParseResult

instance (Shorthand a) => Shorthand (ParseResult a) where
  shorthand (ParseOk a) = "ParseOk" <+> parens (shorthand a)
  shorthand (ParseErr _) = "ParseErr"

-- Module

instance Shorthand Module where
  shorthand modu =
    "Module" <+> braces (hsep (punctuate comma fields))
    where
      fields =
        optionalField docModuleHead (moduleHead modu)
          <> listField docExtensionSetting (moduleLanguagePragmas modu)
          <> listField docImportDecl (moduleImports modu)
          <> listField docDecl (moduleDecls modu)

instance Shorthand Decl where
  shorthand = docDecl

instance Shorthand Expr where
  shorthand = docExpr

instance Shorthand Pattern where
  shorthand = docPattern

instance Shorthand Type where
  shorthand = docType

instance Shorthand LexToken where
  shorthand = docToken

instance Shorthand LexTokenKind where
  shorthand = docTokenKind

docModuleHead :: ModuleHead -> Doc ann
docModuleHead head' =
  "ModuleHead" <+> braces (hsep (punctuate comma fields))
  where
    fields =
      [docText (moduleHeadName head')]
        <> optionalField docPragma (moduleHeadWarningPragma head')
        <> optionalField (brackets . hsep . punctuate comma . map docExportSpec) (moduleHeadExports head')

docExtensionSetting :: ExtensionSetting -> Doc ann
docExtensionSetting setting =
  case setting of
    EnableExtension ext -> pretty (extensionName ext)
    DisableExtension ext -> "DisableExtension" <+> pretty (extensionName ext)

docExportSpec :: ExportSpec -> Doc ann
docExportSpec spec =
  case spec of
    ExportAnn _ sub -> docExportSpec sub
    ExportModule mWarning name ->
      "ExportModule" <> braces (hsep (punctuate comma (optionalField docPragma mWarning <> [docText name])))
    ExportVar mWarning mNamespace name ->
      "ExportVar" <> braces (hsep (punctuate comma (optionalField docPragma mWarning <> optionalField docIENamespace mNamespace <> [docName name])))
    ExportAbs mWarning mNamespace name ->
      "ExportAbs" <> braces (hsep (punctuate comma (optionalField docPragma mWarning <> optionalField docIENamespace mNamespace <> [docName name])))
    ExportAll mWarning mNamespace name ->
      "ExportAll" <> braces (hsep (punctuate comma (optionalField docPragma mWarning <> optionalField docIENamespace mNamespace <> [docName name])))
    ExportWith mWarning mNamespace name members ->
      "ExportWith" <> braces (hsep (punctuate comma fields))
      where
        fields =
          optionalField docPragma mWarning
            <> optionalField docIENamespace mNamespace
            <> [docName name, brackets (hsep (punctuate comma (map docExportMember members)))]
    ExportWithAll mWarning mNamespace name wildcardIndex members ->
      "ExportWithAll" <> braces (hsep (punctuate comma fields))
      where
        fields =
          optionalField docPragma mWarning
            <> optionalField docIENamespace mNamespace
            <> [docName name, pretty wildcardIndex, brackets (hsep (punctuate comma (map docExportMember members)))]

docImportDecl :: ImportDecl -> Doc ann
docImportDecl decl =
  "ImportDecl" <+> braces (hsep (punctuate comma fields))
  where
    fields =
      [docText (importDeclModule decl)]
        <> optionalField docPragma (importDeclSourcePragma decl)
        <> boolField (importDeclSafe decl)
        <> boolField (importDeclQualified decl)
        <> boolField (importDeclQualifiedPost decl)
        <> optionalField docImportLevel (importDeclLevel decl)
        <> optionalField docText (importDeclPackage decl)
        <> optionalField docText (importDeclAs decl)
        <> optionalField docImportSpec (importDeclSpec decl)

docImportLevel :: ImportLevel -> Doc ann
docImportLevel level =
  case level of
    ImportLevelQuote -> "ImportLevelQuote"
    ImportLevelSplice -> "ImportLevelSplice"

docImportSpec :: ImportSpec -> Doc ann
docImportSpec spec =
  "ImportSpec" <+> braces (hsep (punctuate comma fields))
  where
    fields =
      boolField (importSpecHiding spec)
        <> [brackets (hsep (punctuate comma (map docImportItem (importSpecItems spec))))]

docImportItem :: ImportItem -> Doc ann
docImportItem item =
  case item of
    ImportAnn _ sub -> docImportItem sub
    ImportItemVar mNamespace name ->
      "ImportItemVar" <> braces (hsep (punctuate comma (optionalField docIENamespace mNamespace <> [docUnqualifiedName name])))
    ImportItemAbs mNamespace name ->
      "ImportItemAbs" <> braces (hsep (punctuate comma (optionalField docIENamespace mNamespace <> [docUnqualifiedName name])))
    ImportItemAll mNamespace name ->
      "ImportItemAll" <> braces (hsep (punctuate comma (optionalField docIENamespace mNamespace <> [docUnqualifiedName name])))
    ImportItemWith mNamespace name members ->
      "ImportItemWith" <> braces (hsep (punctuate comma fields))
      where
        fields =
          optionalField docIENamespace mNamespace
            <> [docUnqualifiedName name, brackets (hsep (punctuate comma (map docExportMember members)))]
    ImportItemAllWith mNamespace name wildcardIndex members ->
      "ImportItemAllWith" <> braces (hsep (punctuate comma fields))
      where
        fields =
          optionalField docIENamespace mNamespace
            <> [docUnqualifiedName name, pretty wildcardIndex, brackets (hsep (punctuate comma (map docExportMember members)))]

docIENamespace :: IEEntityNamespace -> Doc ann
docIENamespace namespace =
  case namespace of
    IEEntityNamespaceType -> "IEEntityNamespaceType"
    IEEntityNamespacePattern -> "IEEntityNamespacePattern"
    IEEntityNamespaceData -> "IEEntityNamespaceData"

docIEBundledNamespace :: IEBundledNamespace -> Doc ann
docIEBundledNamespace namespace =
  case namespace of
    IEBundledNamespaceType -> "IEBundledNamespaceType"
    IEBundledNamespaceData -> "IEBundledNamespaceData"

docExportMember :: IEBundledMember -> Doc ann
docExportMember (IEBundledMember mNamespace name) =
  "IEBundledMember" <> braces (hsep (punctuate comma (optionalField docIEBundledNamespace mNamespace <> [docName name])))

-- Declarations

docDecl :: Decl -> Doc ann
docDecl decl =
  case decl of
    DeclAnn _ sub -> docDecl sub
    DeclValue vdecl -> "DeclValue" <+> parens (docValueDecl vdecl)
    DeclTypeSig names ty -> "DeclTypeSig" <+> brackets (hsep (punctuate comma (map docUnqualifiedNameText names))) <+> parens (docType ty)
    DeclPatSyn ps -> "DeclPatSyn" <+> parens (docPatSynDecl ps)
    DeclPatSynSig names ty -> "DeclPatSynSig" <+> brackets (hsep (punctuate comma (map docUnqualifiedName names))) <+> parens (docType ty)
    DeclStandaloneKindSig name kind -> "DeclStandaloneKindSig" <+> parens (docUnqualifiedName name) <+> parens (docType kind)
    DeclFixity assoc mNamespace mPrec ops -> "DeclFixity" <+> docFixityAssoc assoc <+> maybe "Nothing" docIENamespace mNamespace <+> maybe "Nothing" pretty mPrec <+> brackets (hsep (punctuate comma (map docUnqualifiedName ops)))
    DeclRoleAnnotation ann -> "DeclRoleAnnotation" <+> parens (docRoleAnnotation ann)
    DeclTypeSyn syn -> "DeclTypeSyn" <+> parens (docTypeSynDecl syn)
    DeclData dd -> "DeclData" <+> parens (docDataDecl dd)
    DeclTypeData dd -> "DeclTypeData" <+> parens (docDataDecl dd)
    DeclNewtype nd -> "DeclNewtype" <+> parens (docNewtypeDecl nd)
    DeclClass cd -> "DeclClass" <+> parens (docClassDecl cd)
    DeclInstance inst -> "DeclInstance" <+> parens (docInstanceDecl inst)
    DeclStandaloneDeriving sd -> "DeclStandaloneDeriving" <+> parens (docStandaloneDerivingDecl sd)
    DeclDefault tys -> "DeclDefault" <+> brackets (hsep (punctuate comma (map docType tys)))
    DeclForeign fd -> "DeclForeign" <+> parens (docForeignDecl fd)
    DeclSplice body -> "DeclSplice" <+> parens (docExpr body)
    DeclTypeFamilyDecl tf -> "DeclTypeFamilyDecl" <+> parens (docTypeFamilyDecl tf)
    DeclDataFamilyDecl df -> "DeclDataFamilyDecl" <+> parens (docDataFamilyDecl df)
    DeclTypeFamilyInst tfi -> "DeclTypeFamilyInst" <+> parens (docTypeFamilyInst tfi)
    DeclDataFamilyInst dfi -> "DeclDataFamilyInst" <+> parens (docDataFamilyInst dfi)
    DeclPragma pragma -> "DeclPragma" <+> docPragma pragma

docValueDecl :: ValueDecl -> Doc ann
docValueDecl vdecl =
  case vdecl of
    FunctionBind name matches -> "FunctionBind" <+> docUnqualifiedNameText name <+> brackets (hsep (punctuate comma (map docMatch matches)))
    PatternBind multTag pat rhs -> "PatternBind" <+> hsep (docPatternBindFields multTag pat rhs)

docPatternBindFields :: MultiplicityTag -> Pattern -> Rhs Expr -> [Doc ann]
docPatternBindFields multTag pat rhs =
  multiplicityFields <> [parens (docPattern pat), parens (docRhs rhs)]
  where
    multiplicityFields =
      case multTag of
        NoMultiplicityTag -> []
        _ -> [docMultiplicityTag multTag]

docMultiplicityTag :: MultiplicityTag -> Doc ann
docMultiplicityTag NoMultiplicityTag = "NoMultiplicityTag"
docMultiplicityTag LinearMultiplicityTag = "LinearMultiplicityTag"
docMultiplicityTag (ExplicitMultiplicityTag ty) = "ExplicitMultiplicityTag" <+> parens (docType ty)

docArrowKind :: ArrowKind -> Doc ann
docArrowKind ArrowUnrestricted = "ArrowUnrestricted"
docArrowKind ArrowLinear = "ArrowLinear"
docArrowKind (ArrowExplicit ty) = "ArrowExplicit" <+> parens (docType ty)

docPatSynDecl :: PatSynDecl -> Doc ann
docPatSynDecl ps =
  "PatSynDecl" <+> braces (hsep (punctuate comma fields))
  where
    fields =
      [docUnqualifiedName (patSynDeclName ps)]
        <> [docPatSynArgs (patSynDeclArgs ps)]
        <> [docPattern (patSynDeclPat ps)]
        <> [docPatSynDir (patSynDeclDir ps)]

docPatSynDir :: PatSynDir -> Doc ann
docPatSynDir dir =
  case dir of
    PatSynUnidirectional -> "PatSynUnidirectional"
    PatSynBidirectional -> "PatSynBidirectional"
    PatSynExplicitBidirectional matches ->
      "PatSynExplicitBidirectional" <+> brackets (hsep (punctuate comma (map docMatch matches)))

docPatSynArgs :: PatSynArgs -> Doc ann
docPatSynArgs args =
  case args of
    PatSynPrefixArgs vars -> "PatSynPrefixArgs" <+> docTextList vars
    PatSynInfixArgs lhs rhs -> "PatSynInfixArgs" <+> docText lhs <+> docText rhs
    PatSynRecordArgs fields' -> "PatSynRecordArgs" <+> docTextList fields'

docMatch :: Match -> Doc ann
docMatch m =
  "Match" <+> braces (hsep (punctuate comma fields))
  where
    fields =
      [ docMatchHeadForm (matchHeadForm m)
      ]
        <> listField docPattern (matchPats m)
        <> [docRhs (matchRhs m)]

docMatchHeadForm :: MatchHeadForm -> Doc ann
docMatchHeadForm headForm =
  case headForm of
    MatchHeadPrefix -> "MatchHeadPrefix"
    MatchHeadInfix -> "MatchHeadInfix"

docRhsWith :: (body -> Doc ann) -> Rhs body -> Doc ann
docRhsWith docBody rhs =
  case rhs of
    UnguardedRhs _ body Nothing -> docBody body
    UnguardedRhs _ body (Just decls) -> docBody body <+> "Just" <+> brackets (hsep (punctuate comma (map docDecl decls)))
    GuardedRhss _ grhss Nothing -> "GuardedRhss" <+> brackets (hsep (punctuate comma (map (docGuardedRhsWith docBody) grhss)))
    GuardedRhss _ grhss (Just decls) -> "GuardedRhss" <+> brackets (hsep (punctuate comma (map (docGuardedRhsWith docBody) grhss))) <+> "Just" <+> brackets (hsep (punctuate comma (map docDecl decls)))

docRhs :: Rhs Expr -> Doc ann
docRhs = docRhsWith docExpr

docGuardedRhsWith :: (body -> Doc ann) -> GuardedRhs body -> Doc ann
docGuardedRhsWith docBody grhs =
  "GuardedRhs" <+> braces (hsep (punctuate comma [brackets (hsep (punctuate comma (map docGuardQualifier (guardedRhsGuards grhs)))), docBody (guardedRhsBody grhs)]))

docGuardedRhs :: GuardedRhs Expr -> Doc ann
docGuardedRhs = docGuardedRhsWith docExpr

docGuardQualifier :: GuardQualifier -> Doc ann
docGuardQualifier gq =
  case gq of
    GuardAnn _ inner -> docGuardQualifier inner
    GuardExpr expr -> "GuardExpr" <+> parens (docExpr expr)
    GuardPat pat expr -> "GuardPat" <+> parens (docPattern pat) <+> parens (docExpr expr)
    GuardLet decls -> "GuardLet" <+> brackets (hsep (punctuate comma (map docDecl decls)))

docTypeSynDecl :: TypeSynDecl -> Doc ann
docTypeSynDecl syn =
  "TypeSynDecl" <+> braces (hsep (punctuate comma fields))
  where
    fields =
      [docBinderHeadValue (typeSynHead syn)]
        <> [docType (typeSynBody syn)]

docRoleAnnotation :: RoleAnnotation -> Doc ann
docRoleAnnotation ann =
  "RoleAnnotation" <+> braces (hsep (punctuate comma fields))
  where
    fields =
      [docUnqualifiedName (roleAnnotationName ann)]
        <> [brackets (hsep (punctuate comma (map docRole (roleAnnotationRoles ann))))]

docRole :: Role -> Doc ann
docRole role =
  case role of
    RoleNominal -> "RoleNominal"
    RoleRepresentational -> "RoleRepresentational"
    RolePhantom -> "RolePhantom"
    RoleInfer -> "RoleInfer"

docDataDecl :: DataDecl -> Doc ann
docDataDecl dd =
  "DataDecl" <+> braces (hsep (punctuate comma fields))
  where
    fields =
      optionalField docPragma (dataDeclCTypePragma dd)
        <> take 1 binderFields
        <> listField docType (dataDeclContext dd)
        <> drop 1 binderFields
        <> optionalField docType (dataDeclKind dd)
        <> listField docDataConDecl (dataDeclConstructors dd)
        <> listField docDerivingClause (dataDeclDeriving dd)
    binderFields = docBinderHead (dataDeclHead dd)

docNewtypeDecl :: NewtypeDecl -> Doc ann
docNewtypeDecl nd =
  "NewtypeDecl" <+> braces (hsep (punctuate comma fields))
  where
    fields =
      optionalField docPragma (newtypeDeclCTypePragma nd)
        <> take 1 binderFields
        <> listField docType (newtypeDeclContext nd)
        <> drop 1 binderFields
        <> optionalField docType (newtypeDeclKind nd)
        <> optionalField docDataConDecl (newtypeDeclConstructor nd)
        <> listField docDerivingClause (newtypeDeclDeriving nd)
    binderFields = docBinderHead (newtypeDeclHead nd)

docDataConDecl :: DataConDecl -> Doc ann
docDataConDecl dcd =
  case dcd of
    DataConAnn _ inner -> docDataConDecl inner
    PrefixCon forallVars constraints name fields' ->
      "PrefixCon" <+> braces (hsep (punctuate comma ([docUnqualifiedName name] <> listField docTyVarBinder forallVars <> listField docType constraints <> listField docBangType fields')))
    InfixCon forallVars constraints lhs op rhs ->
      "InfixCon" <+> braces (hsep (punctuate comma (listField docTyVarBinder forallVars <> listField docType constraints <> [docBangType lhs, docUnqualifiedName op, docBangType rhs])))
    RecordCon forallVars constraints name fields' ->
      "RecordCon" <+> hsep (docRecordConFields forallVars constraints name fields')
    GadtCon forallBinders constraints names body ->
      "GadtCon" <+> braces (hsep (punctuate comma (listField docForallTelescope forallBinders <> listField docType constraints <> listField docUnqualifiedName names <> [docGadtBody body])))
    TupleCon forallVars constraints flavor fields' ->
      "TupleCon" <+> braces (hsep (punctuate comma (listField docTyVarBinder forallVars <> listField docType constraints <> [pretty (show flavor)] <> listField docBangType fields')))
    UnboxedSumCon forallVars constraints pos arity field' ->
      "UnboxedSumCon" <+> braces (hsep (punctuate comma (listField docTyVarBinder forallVars <> listField docType constraints <> [pretty (show pos), pretty (show arity), docBangType field'])))
    ListCon forallVars constraints ->
      "ListCon" <+> braces (hsep (punctuate comma (listField docTyVarBinder forallVars <> listField docType constraints)))

-- | Document a GADT body
docGadtBody :: GadtBody -> Doc ann
docGadtBody body =
  case body of
    GadtPrefixBody args resultTy ->
      "GadtPrefixBody" <+> braces (hsep (punctuate comma (listField docGadtArg args <> [docType resultTy])))
    GadtRecordBody fields' resultTy ->
      "GadtRecordBody" <+> braces (hsep (punctuate comma (listField docFieldDecl fields' <> [docType resultTy])))

docGadtArg :: (BangType, ArrowKind) -> Doc ann
docGadtArg (bt, ArrowUnrestricted) = parens (docBangType bt)
docGadtArg (bt, ak) = parens (docBangType bt <+> "," <+> docArrowKind ak)

docBangType :: BangType -> Doc ann
docBangType bt =
  "BangType" <+> braces (hsep (punctuate comma fields))
  where
    fields =
      listField docPragma (bangPragmas bt)
        <> boolField (bangStrict bt)
        <> boolField (bangLazy bt)
        <> [docType (bangType bt)]

docFieldDecl :: FieldDecl -> Doc ann
docFieldDecl fd =
  "FieldDecl" <+> brackets (hsep (punctuate comma (map docUnqualifiedNameText (fieldNames fd)))) <+> parens (docFieldDeclType (fieldType fd))

docFieldDeclType :: BangType -> Doc ann
docFieldDeclType bt
  | null (bangPragmas bt),
    not (bangStrict bt),
    not (bangLazy bt) =
      docType (bangType bt)
  | otherwise = docBangType bt

docRecordConFields :: [TyVarBinder] -> [Type] -> UnqualifiedName -> [FieldDecl] -> [Doc ann]
docRecordConFields forallVars constraints name fields' =
  docUnqualifiedNameText name
    : listField docTyVarBinder forallVars
      <> listField docType constraints
      <> [brackets (hsep (punctuate comma (map docFieldDecl fields')))]

docDerivingClause :: DerivingClause -> Doc ann
docDerivingClause dc =
  "DerivingClause" <+> braces (hsep (punctuate comma fields))
  where
    fields =
      optionalField docDerivingStrategy (derivingStrategy dc)
        <> [docDerivingClasses (derivingClasses dc)]

docDerivingClasses :: Either Name [Type] -> Doc ann
docDerivingClasses classes =
  case classes of
    Left name -> docName name
    Right types -> brackets (hsep (punctuate comma (map docType types)))

docDerivingStrategy :: DerivingStrategy -> Doc ann
docDerivingStrategy ds =
  case ds of
    DerivingStock -> "DerivingStock"
    DerivingNewtype -> "DerivingNewtype"
    DerivingAnyclass -> "DerivingAnyclass"
    DerivingVia ty -> "DerivingVia" <+> docType ty

docClassDecl :: ClassDecl -> Doc ann
docClassDecl cd =
  "ClassDecl" <+> braces (hsep (punctuate comma fields))
  where
    fields =
      optionalField (brackets . hsep . punctuate comma . map docType) (classDeclContext cd)
        <> binderFields
        <> listField docFunctionalDependency (classDeclFundeps cd)
        <> listField docClassDeclItem (classDeclItems cd)
    binderFields = docBinderHead (classDeclHead cd)

docFunctionalDependency :: FunctionalDependency -> Doc ann
docFunctionalDependency dep =
  "FunctionalDependency" <+> braces (hsep (punctuate comma fields))
  where
    fields =
      listField docText (functionalDependencyDeterminers dep)
        <> listField docText (functionalDependencyDetermined dep)

docTypeFamilyInjectivity :: TypeFamilyInjectivity -> Doc ann
docTypeFamilyInjectivity injectivity =
  "TypeFamilyInjectivity" <+> braces (hsep (punctuate comma fields))
  where
    fields =
      [ docText (typeFamilyInjectivityResult injectivity)
      ]
        <> listField docText (typeFamilyInjectivityDetermined injectivity)

docTypeFamilyResultSig :: TypeFamilyResultSig -> Doc ann
docTypeFamilyResultSig sig =
  case sig of
    TypeFamilyKindSig kind -> "TypeFamilyKindSig" <+> parens (docType kind)
    TypeFamilyTyVarSig result ->
      "TypeFamilyTyVarSig" <+> braces (hsep (punctuate comma [docTyVarBinder result]))
    TypeFamilyInjectiveSig result injectivity ->
      "TypeFamilyInjectiveSig" <+> braces (hsep (punctuate comma [docTyVarBinder result, docTypeFamilyInjectivity injectivity]))

docClassDeclItem :: ClassDeclItem -> Doc ann
docClassDeclItem item =
  case item of
    ClassItemAnn _ sub -> docClassDeclItem sub
    ClassItemTypeSig names ty -> "ClassItemTypeSig" <+> braces (hsep (punctuate comma [brackets (hsep (punctuate comma (map docUnqualifiedName names))), docType ty]))
    ClassItemDefaultSig name ty -> "ClassItemDefaultSig" <+> braces (hsep (punctuate comma [docUnqualifiedName name, docType ty]))
    ClassItemFixity assoc mNamespace mPrec ops -> "ClassItemFixity" <+> braces (hsep (punctuate comma ([docFixityAssoc assoc] <> optionalField docIENamespace mNamespace <> optionalField pretty mPrec <> [brackets (hsep (punctuate comma (map docUnqualifiedName ops)))])))
    ClassItemDefault vdecl -> "ClassItemDefault" <+> parens (docValueDecl vdecl)
    ClassItemTypeFamilyDecl tf -> "ClassItemTypeFamilyDecl" <+> parens (docTypeFamilyDecl tf)
    ClassItemDataFamilyDecl df -> "ClassItemDataFamilyDecl" <+> parens (docDataFamilyDecl df)
    ClassItemDefaultTypeInst tfi -> "ClassItemDefaultTypeInst" <+> parens (docTypeFamilyInst tfi)
    ClassItemPragma pragma -> "ClassItemPragma" <+> docPragma pragma

docInstanceDecl :: InstanceDecl -> Doc ann
docInstanceDecl inst =
  "InstanceDecl" <+> braces (hsep (punctuate comma fields))
  where
    fields =
      listField docPragma (instanceDeclPragmas inst)
        <> optionalField docPragma (instanceDeclWarning inst)
        <> listField docTyVarBinder (instanceDeclForall inst)
        <> listField docType (instanceDeclContext inst)
        <> [docType (instanceDeclHead inst)]
        <> listField docInstanceDeclItem (instanceDeclItems inst)

docInstanceDeclItem :: InstanceDeclItem -> Doc ann
docInstanceDeclItem item =
  case item of
    InstanceItemAnn _ inner -> docInstanceDeclItem inner
    InstanceItemBind vdecl -> "InstanceItemBind" <+> parens (docValueDecl vdecl)
    InstanceItemTypeSig names ty -> "InstanceItemTypeSig" <+> braces (hsep (punctuate comma [brackets (hsep (punctuate comma (map docUnqualifiedName names))), docType ty]))
    InstanceItemFixity assoc mNamespace mPrec ops -> "InstanceItemFixity" <+> braces (hsep (punctuate comma ([docFixityAssoc assoc] <> optionalField docIENamespace mNamespace <> optionalField pretty mPrec <> [brackets (hsep (punctuate comma (map docUnqualifiedName ops)))])))
    InstanceItemTypeFamilyInst tfi -> "InstanceItemTypeFamilyInst" <+> parens (docTypeFamilyInst tfi)
    InstanceItemDataFamilyInst dfi -> "InstanceItemDataFamilyInst" <+> parens (docDataFamilyInst dfi)
    InstanceItemPragma pragma -> "InstanceItemPragma" <+> docPragma pragma

docStandaloneDerivingDecl :: StandaloneDerivingDecl -> Doc ann
docStandaloneDerivingDecl sd =
  "StandaloneDerivingDecl" <+> braces (hsep (punctuate comma fields))
  where
    fields =
      optionalField docDerivingStrategy (standaloneDerivingStrategy sd)
        <> listField docPragma (standaloneDerivingPragmas sd)
        <> optionalField docPragma (standaloneDerivingWarning sd)
        <> listField docType (standaloneDerivingContext sd)
        <> [docType (standaloneDerivingHead sd)]

docInstanceOverlapPragma :: InstanceOverlapPragma -> Doc ann
docInstanceOverlapPragma pragma' =
  case pragma' of
    Overlapping -> "Overlapping"
    Overlappable -> "Overlappable"
    Overlaps -> "Overlaps"
    Incoherent -> "Incoherent"

docPragmaUnpackKind :: PragmaUnpackKind -> Doc ann
docPragmaUnpackKind kind =
  case kind of
    UnpackPragma -> "UnpackPragma"
    NoUnpackPragma -> "NoUnpackPragma"

docPragmaType :: PragmaType -> Doc ann
docPragmaType pt =
  case pt of
    PragmaLanguage settings -> "PragmaLanguage" <+> brackets (hsep (punctuate comma (map docExtensionSetting settings)))
    PragmaInstanceOverlap overlapPragma -> "PragmaInstanceOverlap" <+> docInstanceOverlapPragma overlapPragma
    PragmaWarning msg -> "PragmaWarning" <+> docText msg
    PragmaDeprecated msg -> "PragmaDeprecated" <+> docText msg
    PragmaInline kind body -> "PragmaInline" <+> docText kind <+> docText body
    PragmaUnpack unpackKind -> "PragmaUnpack" <+> docPragmaUnpackKind unpackKind
    PragmaSource sourceText _ -> "PragmaSource" <+> docText sourceText
    PragmaSCC label -> "PragmaSCC" <+> docText label
    PragmaUnknown text -> "PragmaUnknown" <+> docText text

docPragma :: Pragma -> Doc ann
docPragma pragma = docPragmaType (pragmaType pragma)

docForeignDecl :: ForeignDecl -> Doc ann
docForeignDecl fd =
  "ForeignDecl" <+> braces (hsep (punctuate comma fields))
  where
    fields =
      [docForeignDirection (foreignDirection fd)]
        <> [docCallConv (foreignCallConv fd)]
        <> optionalField docForeignSafety (foreignSafety fd)
        <> [docForeignEntitySpec (foreignEntity fd)]
        <> [docUnqualifiedName (foreignName fd)]
        <> [docType (foreignType fd)]

docForeignDirection :: ForeignDirection -> Doc ann
docForeignDirection fd =
  case fd of
    ForeignImport -> "ForeignImport"
    ForeignExport -> "ForeignExport"

docCallConv :: CallConv -> Doc ann
docCallConv cc =
  case cc of
    CCall -> "CCall"
    StdCall -> "StdCall"
    CApi -> "CApi"
    CPrim -> "CPrim"
    JavaScript -> "JavaScript"

docForeignSafety :: ForeignSafety -> Doc ann
docForeignSafety fs =
  case fs of
    Safe -> "Safe"
    Unsafe -> "Unsafe"
    Interruptible -> "Interruptible"

docForeignEntitySpec :: ForeignEntitySpec -> Doc ann
docForeignEntitySpec spec =
  case spec of
    ForeignEntityDynamic -> "ForeignEntityDynamic"
    ForeignEntityWrapper -> "ForeignEntityWrapper"
    ForeignEntityStatic mName -> "ForeignEntityStatic" <> optionalField' docText mName
    ForeignEntityAddress mName -> "ForeignEntityAddress" <> optionalField' docText mName
    ForeignEntityNamed name -> "ForeignEntityNamed" <+> docText name
    ForeignEntityOmitted -> "ForeignEntityOmitted"

docFixityAssoc :: FixityAssoc -> Doc ann
docFixityAssoc fa =
  case fa of
    Infix -> "Infix"
    InfixL -> "InfixL"
    InfixR -> "InfixR"

-- Types

docType :: Type -> Doc ann
docType ty =
  case ty of
    TVar name -> "TVar" <+> docUnqualifiedNameText name
    TCon name promoted ->
      "TCon"
        <+> docName name
        <> (if promoted == Promoted then " Promoted" else "")
    TBuiltinCon con -> "TBuiltinCon" <+> pretty (show con)
    TImplicitParam name inner -> "TImplicitParam" <+> docText name <+> parens (docType inner)
    TTypeLit lit -> "TTypeLit" <+> docTypeLiteral lit
    TStar {} -> "TStar"
    TQuasiQuote quoter body -> "TQuasiQuote" <+> docText quoter <+> docText body
    TForall telescope inner ->
      case forallTelescopeVisibility telescope of
        ForallInvisible ->
          "TForall"
            <+> brackets (hsep (punctuate comma (map docTyVarBinder (forallTelescopeBinders telescope))))
            <+> parens (docType inner)
        ForallVisible -> "TForall" <+> parens (docForallTelescope telescope) <+> parens (docType inner)
    TApp f x -> "TApp" <+> parens (docType f) <+> parens (docType x)
    TTypeApp f x -> "TTypeApp" <+> parens (docType f) <+> parens (docType x)
    TInfix lhs op promoted rhs ->
      "TInfix"
        <+> parens (docType lhs)
        <+> docName op
        <> (if promoted == Promoted then " Promoted" else "")
        <+> parens (docType rhs)
    TFun arrowKind a b -> "TFun" <+> hsep (docFunctionTypeFields arrowKind a b)
    TTuple tupleFlavor promoted elems ->
      "TTuple" <+> hsep (docTypeTupleFields tupleFlavor promoted elems)
    TUnboxedSum elems ->
      "TUnboxedSum"
        <+> brackets (hsep (punctuate comma (map docType elems)))
    TList promoted elems ->
      "TList" <+> hsep (docTypeListFields promoted elems)
    TParen inner -> "TParen" <+> parens (docType inner)
    TKindSig ty' kind -> "TKindSig" <+> parens (docType ty') <+> parens (docType kind)
    TContext constraints inner -> "TContext" <+> brackets (hsep (punctuate comma (map docType constraints))) <+> parens (docType inner)
    TSplice body -> "TSplice" <+> parens (docExpr body)
    TWildcard -> "TWildcard"
    TAnn _ sub -> docType sub

docTypeLiteral :: TypeLiteral -> Doc ann
docTypeLiteral lit =
  case lit of
    TypeLitInteger n _ -> "TypeLitInteger" <+> pretty n
    TypeLitSymbol s _ -> "TypeLitSymbol" <+> docText s
    TypeLitChar c _ -> "TypeLitChar" <+> pretty (show c)

docFunctionTypeFields :: ArrowKind -> Type -> Type -> [Doc ann]
docFunctionTypeFields arrowKind a b =
  arrowFields <> [parens (docType a), parens (docType b)]
  where
    arrowFields =
      case arrowKind of
        ArrowUnrestricted -> []
        _ -> [docArrowKind arrowKind]

docTypeListFields :: TypePromotion -> [Type] -> [Doc ann]
docTypeListFields promoted elems =
  promotedFields <> [brackets (hsep (punctuate comma (map docType elems)))]
  where
    promotedFields =
      case promoted of
        Unpromoted -> []
        Promoted -> ["Promoted"]

docTypeTupleFields :: TupleFlavor -> TypePromotion -> [Type] -> [Doc ann]
docTypeTupleFields tupleFlavor promoted elems =
  flavorFields <> promotedFields <> [brackets (hsep (punctuate comma (map docType elems)))]
  where
    flavorFields =
      case tupleFlavor of
        Boxed -> []
        _ -> [pretty (show tupleFlavor)]

    promotedFields =
      case promoted of
        Unpromoted -> []
        Promoted -> ["Promoted"]

docTyVarBinder :: TyVarBinder -> Doc ann
docTyVarBinder tvb =
  "TyVarBinder" <+> braces (hsep (punctuate comma fields))
  where
    fields =
      [docText (tyVarBinderName tvb)]
        <> optionalField (\kind -> "Just" <+> parens (docType kind)) (tyVarBinderKind tvb)
        <> optionalField docTyVarBSpecificity (specificityField tvb)
        <> optionalField docTyVarBVisibility (visibilityField tvb)

    specificityField binder =
      case tyVarBinderSpecificity binder of
        TyVarBSpecified -> Nothing
        specificity -> Just specificity

    visibilityField binder =
      case tyVarBinderVisibility binder of
        TyVarBVisible -> Nothing
        visibility -> Just visibility

docTyVarBSpecificity :: TyVarBSpecificity -> Doc ann
docTyVarBSpecificity specificity =
  case specificity of
    TyVarBInferred -> "TyVarBInferred"
    TyVarBSpecified -> "TyVarBSpecified"

docTyVarBVisibility :: TyVarBVisibility -> Doc ann
docTyVarBVisibility visibility =
  case visibility of
    TyVarBVisible -> "TyVarBVisible"
    TyVarBInvisible -> "TyVarBInvisible"

docForallVis :: ForallVis -> Doc ann
docForallVis visibility =
  case visibility of
    ForallInvisible -> "ForallInvisible"
    ForallVisible -> "ForallVisible"

docForallTelescope :: ForallTelescope -> Doc ann
docForallTelescope telescope =
  "ForallTelescope"
    <+> braces
      ( hsep
          ( punctuate
              comma
              ( [docForallVis (forallTelescopeVisibility telescope)]
                  <> listField docTyVarBinder (forallTelescopeBinders telescope)
              )
          )
      )

-- Patterns

docPattern :: Pattern -> Doc ann
docPattern pat =
  case pat of
    PAnn _ sub -> docPattern sub
    PVar name -> "PVar" <+> docUnqualifiedNameText name
    PTypeBinder binder -> "PTypeBinder" <+> parens (docTyVarBinder binder)
    PTypeSyntax form ty -> "PTypeSyntax" <+> docTypeSyntaxForm form <+> parens (docType ty)
    PWildcard -> "PWildcard"
    PLit lit -> "PLit" <+> parens (docLiteral lit)
    PQuasiQuote quoter body -> "PQuasiQuote" <+> docText quoter <+> docText body
    PTuple tupleFlavor elems ->
      "PTuple" <+> hsep (docPatternTupleFields tupleFlavor elems)
    PUnboxedSum altIdx arity inner ->
      "PUnboxedSum" <+> pretty altIdx <+> pretty arity <+> docPattern inner
    PList elems -> "PList" <+> brackets (hsep (punctuate comma (map docPattern elems)))
    PCon name typeArgs args ->
      case typeArgs of
        [] -> "PCon" <+> docName name <+> brackets (hsep (punctuate comma (map docPattern args)))
        _ ->
          "PCon"
            <+> docName name
            <+> braces
              ( hsep
                  ( punctuate
                      comma
                      ( listField docType typeArgs
                          <> listField docPattern args
                      )
                  )
              )
    PInfix lhs op rhs -> "PInfix" <+> parens (docPattern lhs) <+> docName op <+> parens (docPattern rhs)
    PView expr inner -> "PView" <+> parens (docExpr expr) <+> parens (docPattern inner)
    PAs name inner -> "PAs" <+> docUnqualifiedName name <+> parens (docPattern inner)
    PStrict inner -> "PStrict" <+> parens (docPattern inner)
    PIrrefutable inner -> "PIrrefutable" <+> parens (docPattern inner)
    PNegLit lit -> "PNegLit" <+> parens (docLiteral lit)
    PParen inner -> "PParen" <+> parens (docPattern inner)
    PRecord name fields' hasWildcard -> "PRecord" <+> docName name <+> braces (hsep (punctuate comma ([docPatternRecordField recordField | recordField <- fields'] ++ [".." | hasWildcard])))
    PTypeSig inner ty -> "PTypeSig" <+> parens (docPattern inner) <+> parens (docType ty)
    PSplice body -> "PSplice" <+> parens (docExpr body)

docPatternTupleFields :: TupleFlavor -> [Pattern] -> [Doc ann]
docPatternTupleFields tupleFlavor elems =
  flavorFields <> [brackets (hsep (punctuate comma (map docPattern elems)))]
  where
    flavorFields =
      case tupleFlavor of
        Boxed -> []
        _ -> [pretty (show tupleFlavor)]

docLiteral :: Literal -> Doc ann
docLiteral lit =
  case peelLiteralAnn lit of
    LitInt n nt _ -> "LitInt" <+> pretty n <+> docNumericType nt
    LitFloat n ft _ -> "LitFloat" <+> pretty (show n) <+> docFloatType ft
    LitChar c _ -> "LitChar" <+> pretty (show c)
    LitCharHash c repr -> "LitCharHash" <+> pretty (show c) <+> docText repr
    LitString s _ -> "LitString" <+> docText s
    LitStringHash s repr -> "LitStringHash" <+> docText s <+> docText repr
    LitAnn {} -> error "unreachable"

-- Expressions

docExpr :: Expr -> Doc ann
docExpr expr =
  case expr of
    EVar name -> "EVar" <+> docName name
    ETypeSyntax form ty -> "ETypeSyntax" <+> docTypeSyntaxForm form <+> parens (docType ty)
    EInt n nt _ -> "EInt" <+> pretty n <+> docNumericType nt
    EFloat n ft _ -> "EFloat" <+> pretty (show n) <+> docFloatType ft
    EChar c _ -> "EChar" <+> pretty (show c)
    ECharHash c repr -> "ECharHash" <+> pretty (show c) <+> docText repr
    EString s _ -> "EString" <+> docText s
    EStringHash s repr -> "EStringHash" <+> docText s <+> docText repr
    EOverloadedLabel label raw -> "EOverloadedLabel" <+> docText label <+> docText raw
    EQuasiQuote quoter body -> "EQuasiQuote" <+> docText quoter <+> docText body
    ETHExpQuote body -> "ETHExpQuote" <+> parens (docExpr body)
    ETHTypedQuote body -> "ETHTypedQuote" <+> parens (docExpr body)
    ETHDeclQuote decls -> "ETHDeclQuote" <+> brackets (hsep (punctuate comma (map docDecl decls)))
    ETHTypeQuote ty -> "ETHTypeQuote" <+> parens (docType ty)
    ETHPatQuote pat -> "ETHPatQuote" <+> parens (docPattern pat)
    ETHNameQuote body -> "ETHNameQuote" <+> parens (docExpr body)
    ETHTypeNameQuote ty -> "ETHTypeNameQuote" <+> parens (docType ty)
    ETHSplice body -> "ETHSplice" <+> parens (docExpr body)
    ETHTypedSplice body -> "ETHTypedSplice" <+> parens (docExpr body)
    EIf cond yes no -> "EIf" <+> parens (docExpr cond) <+> parens (docExpr yes) <+> parens (docExpr no)
    EMultiWayIf rhss -> "EMultiWayIf" <+> brackets (hsep (punctuate comma (map docGuardedRhs rhss)))
    ELambdaPats pats body -> "ELambdaPats" <+> brackets (hsep (punctuate comma (map docPattern pats))) <+> parens (docExpr body)
    ELambdaCase alts -> "ELambdaCase" <+> brackets (hsep (punctuate comma (map docCaseAlt alts)))
    ELambdaCases alts -> "ELambdaCases" <+> brackets (hsep (punctuate comma (map docLambdaCaseAlt alts)))
    EInfix lhs op rhs -> "EInfix" <+> parens (docExpr lhs) <+> docName op <+> parens (docExpr rhs)
    ENegate inner -> "ENegate" <+> parens (docExpr inner)
    ESectionL lhs op -> "ESectionL" <+> parens (docExpr lhs) <+> docName op
    ESectionR op rhs -> "ESectionR" <+> docName op <+> parens (docExpr rhs)
    ELetDecls decls body -> "ELetDecls" <+> brackets (hsep (punctuate comma (map docDecl decls))) <+> parens (docExpr body)
    ECase scrutinee alts -> "ECase" <+> parens (docExpr scrutinee) <+> brackets (hsep (punctuate comma (map docCaseAlt alts)))
    EDo stmts flavor -> "EDo" <+> hsep (brackets (hsep (punctuate comma (map docDoStmt stmts))) : docDoFlavorFields flavor)
    EListComp body quals -> "EListComp" <+> parens (docExpr body) <+> brackets (hsep (punctuate comma (map docCompStmt quals)))
    EListCompParallel body qualGroups -> "EListCompParallel" <+> parens (docExpr body) <+> brackets (hsep (punctuate "|" [brackets (hsep (punctuate comma (map docCompStmt qs))) | qs <- qualGroups]))
    EArithSeq seqInfo -> "EArithSeq" <+> parens (docArithSeq seqInfo)
    ERecordCon name fields' hasWildcard -> "ERecordCon" <+> docName name <+> braces (hsep (punctuate comma ([docExprRecordField recordField | recordField <- fields'] ++ [".." | hasWildcard])))
    ERecordUpd base fields' -> "ERecordUpd" <+> parens (docExpr base) <+> braces (hsep (punctuate comma [docExprRecordField recordField | recordField <- fields']))
    EGetField base fieldName -> "EGetField" <+> parens (docExpr base) <+> docName fieldName
    EGetFieldProjection fields -> "EGetFieldProjection" <+> brackets (hsep (punctuate comma (map docName fields)))
    ETypeSig inner ty -> "ETypeSig" <+> parens (docExpr inner) <+> parens (docType ty)
    EParen inner -> "EParen" <+> parens (docExpr inner)
    EList elems -> "EList" <+> brackets (hsep (punctuate comma (map docExpr elems)))
    ETuple tupleFlavor elems ->
      "ETuple" <+> hsep (docExprTupleFields tupleFlavor elems)
    EUnboxedSum altIdx arity inner ->
      "EUnboxedSum" <+> pretty altIdx <+> pretty arity <+> docExpr inner
    ETypeApp inner ty -> "ETypeApp" <+> parens (docExpr inner) <+> parens (docType ty)
    EApp f x -> "EApp" <+> parens (docExpr f) <+> parens (docExpr x)
    EProc pat body -> "EProc" <+> parens (docPattern pat) <+> parens (docCmd body)
    EPragma pragma inner -> "EPragma" <+> parens (docPragma pragma) <+> parens (docExpr inner)
    EAnn _ sub -> docExpr sub

docPatternRecordField :: RecordField Pattern -> Doc ann
docPatternRecordField recordField =
  docName (recordFieldName recordField)
    <+> (if recordFieldPun recordField then "~pun" else "=")
    <+> docPattern (recordFieldValue recordField)

docExprRecordField :: RecordField Expr -> Doc ann
docExprRecordField recordField =
  docName (recordFieldName recordField)
    <+> (if recordFieldPun recordField then "~pun" else "=")
    <+> docExpr (recordFieldValue recordField)

docMaybeExpr :: Maybe Expr -> Doc ann
docMaybeExpr Nothing = "Nothing"
docMaybeExpr (Just expr) = docExpr expr

docCaseAltWith :: (body -> Doc ann) -> CaseAlt body -> Doc ann
docCaseAltWith docBody (CaseAlt _ pat rhs) =
  "CaseAlt" <+> parens (docPattern pat) <+> parens (docRhsWith docBody rhs)

docCaseAlt :: CaseAlt Expr -> Doc ann
docCaseAlt = docCaseAltWith docExpr

docLambdaCaseAlt :: LambdaCaseAlt -> Doc ann
docLambdaCaseAlt (LambdaCaseAlt _ pats rhs) =
  "LambdaCaseAlt" <+> brackets (hsep (punctuate comma (map docPattern pats))) <+> parens (docRhs rhs)

docDoFlavorFields :: DoFlavor -> [Doc ann]
docDoFlavorFields flavor =
  case flavor of
    DoPlain -> []
    DoMdo -> flavorField "DoMdo"
    DoQualified m -> flavorField ("DoQualified" <+> docText m)
    DoQualifiedMdo m -> flavorField ("DoQualifiedMdo" <+> docText m)
  where
    flavorField doc = [parens doc]

docExprTupleFields :: TupleFlavor -> [Maybe Expr] -> [Doc ann]
docExprTupleFields tupleFlavor elems =
  flavorFields <> [brackets (hsep (punctuate comma (map docMaybeExpr elems)))]
  where
    flavorFields =
      case tupleFlavor of
        Boxed -> []
        _ -> [pretty (show tupleFlavor)]

docDoStmt :: DoStmt Expr -> Doc ann
docDoStmt stmt =
  case stmt of
    DoAnn _ inner -> docDoStmt inner
    DoBind pat expr -> "DoBind" <+> parens (docPattern pat) <+> parens (docExpr expr)
    DoLetDecls decls -> "DoLetDecls" <+> brackets (hsep (punctuate comma (map docDecl decls)))
    DoExpr expr -> "DoExpr" <+> parens (docExpr expr)
    DoRecStmt stmts -> "DoRecStmt" <+> brackets (hsep (punctuate comma (map docDoStmt stmts)))

docCompStmt :: CompStmt -> Doc ann
docCompStmt stmt =
  case stmt of
    CompAnn _ inner -> docCompStmt inner
    CompGen pat expr -> "CompGen" <+> parens (docPattern pat) <+> parens (docExpr expr)
    CompGuard expr -> "CompGuard" <+> parens (docExpr expr)
    CompLetDecls decls -> "CompLetDecls" <+> brackets (hsep (punctuate comma (map docDecl decls)))
    CompThen f -> "CompThen" <+> parens (docExpr f)
    CompThenBy f e -> "CompThenBy" <+> parens (docExpr f) <+> parens (docExpr e)
    CompGroupUsing f -> "CompGroupUsing" <+> parens (docExpr f)
    CompGroupByUsing e f -> "CompGroupByUsing" <+> parens (docExpr e) <+> parens (docExpr f)

docCmd :: Cmd -> Doc ann
docCmd cmd =
  case cmd of
    CmdAnn _ inner -> docCmd inner
    CmdArrApp lhs HsFirstOrderApp rhs -> "CmdArrApp" <+> parens (docExpr lhs) <+> "HsFirstOrderApp" <+> parens (docExpr rhs)
    CmdArrApp lhs HsHigherOrderApp rhs -> "CmdArrApp" <+> parens (docExpr lhs) <+> "HsHigherOrderApp" <+> parens (docExpr rhs)
    CmdInfix l op r -> "CmdInfix" <+> parens (docCmd l) <+> docName op <+> parens (docCmd r)
    CmdDo stmts -> "CmdDo" <+> brackets (hsep (punctuate comma (map docCmdStmt stmts)))
    CmdIf cond yes no -> "CmdIf" <+> parens (docExpr cond) <+> parens (docCmd yes) <+> parens (docCmd no)
    CmdCase scrut alts -> "CmdCase" <+> parens (docExpr scrut) <+> brackets (hsep (punctuate comma (map docCmdCaseAlt alts)))
    CmdLet decls body -> "CmdLet" <+> brackets (hsep (punctuate comma (map docDecl decls))) <+> parens (docCmd body)
    CmdLam pats body -> "CmdLam" <+> brackets (hsep (punctuate comma (map docPattern pats))) <+> parens (docCmd body)
    CmdApp c e -> "CmdApp" <+> parens (docCmd c) <+> parens (docExpr e)
    CmdPar c -> "CmdPar" <+> parens (docCmd c)

docCmdStmt :: DoStmt Cmd -> Doc ann
docCmdStmt stmt =
  case stmt of
    DoAnn _ inner -> docCmdStmt inner
    DoBind pat cmd' -> "DoBind" <+> parens (docPattern pat) <+> parens (docCmd cmd')
    DoLetDecls decls -> "DoLetDecls" <+> brackets (hsep (punctuate comma (map docDecl decls)))
    DoExpr cmd' -> "DoExpr" <+> parens (docCmd cmd')
    DoRecStmt stmts -> "DoRecStmt" <+> brackets (hsep (punctuate comma (map docCmdStmt stmts)))

docCmdCaseAlt :: CaseAlt Cmd -> Doc ann
docCmdCaseAlt = docCaseAltWith docCmd

docArithSeq :: ArithSeq -> Doc ann
docArithSeq seqInfo =
  case seqInfo of
    ArithSeqAnn _ inner -> docArithSeq inner
    ArithSeqFrom from -> "ArithSeqFrom" <+> parens (docExpr from)
    ArithSeqFromThen from thn -> "ArithSeqFromThen" <+> parens (docExpr from) <+> parens (docExpr thn)
    ArithSeqFromTo from to -> "ArithSeqFromTo" <+> parens (docExpr from) <+> parens (docExpr to)
    ArithSeqFromThenTo from thn to -> "ArithSeqFromThenTo" <+> parens (docExpr from) <+> parens (docExpr thn) <+> parens (docExpr to)

-- Token pretty printing

docToken :: LexToken -> Doc ann
docToken tok = docTokenKind (lexTokenKind tok)

docTokenKind :: LexTokenKind -> Doc ann
docTokenKind kind =
  case kind of
    TkKeywordCase -> "TkKeywordCase"
    TkKeywordClass -> "TkKeywordClass"
    TkKeywordData -> "TkKeywordData"
    TkKeywordDefault -> "TkKeywordDefault"
    TkKeywordDeriving -> "TkKeywordDeriving"
    TkKeywordDo -> "TkKeywordDo"
    TkKeywordElse -> "TkKeywordElse"
    TkKeywordForall -> "TkKeywordForall"
    TkKeywordForeign -> "TkKeywordForeign"
    TkKeywordIf -> "TkKeywordIf"
    TkKeywordImport -> "TkKeywordImport"
    TkKeywordIn -> "TkKeywordIn"
    TkKeywordInfix -> "TkKeywordInfix"
    TkKeywordInfixl -> "TkKeywordInfixl"
    TkKeywordInfixr -> "TkKeywordInfixr"
    TkKeywordInstance -> "TkKeywordInstance"
    TkKeywordLet -> "TkKeywordLet"
    TkKeywordModule -> "TkKeywordModule"
    TkKeywordNewtype -> "TkKeywordNewtype"
    TkKeywordOf -> "TkKeywordOf"
    TkKeywordThen -> "TkKeywordThen"
    TkKeywordType -> "TkKeywordType"
    TkKeywordWhere -> "TkKeywordWhere"
    TkKeywordUnderscore -> "TkKeywordUnderscore"
    TkKeywordProc -> "TkKeywordProc"
    TkKeywordPattern -> "TkKeywordPattern"
    TkKeywordRec -> "TkKeywordRec"
    TkKeywordMdo -> "TkKeywordMdo"
    TkKeywordBy -> "TkKeywordBy"
    TkKeywordUsing -> "TkKeywordUsing"
    TkQualifiedDo modName -> "TkQualifiedDo" <+> docText modName
    TkQualifiedMdo modName -> "TkQualifiedMdo" <+> docText modName
    TkPrefixPercent -> "TkPrefixPercent"
    TkLinearArrow -> "TkLinearArrow"
    TkArrowTail -> "TkArrowTail"
    TkArrowTailReverse -> "TkArrowTailReverse"
    TkDoubleArrowTail -> "TkDoubleArrowTail"
    TkDoubleArrowTailReverse -> "TkDoubleArrowTailReverse"
    TkBananaOpen -> "TkBananaOpen"
    TkBananaClose -> "TkBananaClose"
    TkReservedDotDot -> "TkReservedDotDot"
    TkReservedColon -> "TkReservedColon"
    TkReservedDoubleColon -> "TkReservedDoubleColon"
    TkReservedEquals -> "TkReservedEquals"
    TkReservedBackslash -> "TkReservedBackslash"
    TkReservedPipe -> "TkReservedPipe"
    TkReservedLeftArrow -> "TkReservedLeftArrow"
    TkReservedRightArrow -> "TkReservedRightArrow"
    TkReservedAt -> "TkReservedAt"
    TkReservedDoubleArrow -> "TkReservedDoubleArrow"
    TkTypeApp -> "TkTypeApp"
    TkVarId name -> "TkVarId" <+> docText name
    TkConId name -> "TkConId" <+> docText name
    TkQVarId modName name -> "TkQVarId" <+> docText modName <+> docText name
    TkQConId modName name -> "TkQConId" <+> docText modName <+> docText name
    TkImplicitParam name -> "TkImplicitParam" <+> docText name
    TkVarSym name -> "TkVarSym" <+> docText name
    TkConSym name -> "TkConSym" <+> docText name
    TkQVarSym modName name -> "TkQVarSym" <+> docText modName <+> docText name
    TkQConSym modName name -> "TkQConSym" <+> docText modName <+> docText name
    TkInteger n nt -> "TkInteger" <+> pretty n <+> docNumericType nt
    TkFloat n ft -> "TkFloat" <+> pretty (show n) <+> docFloatType ft
    TkChar c -> "TkChar" <+> pretty (show c)
    TkCharHash c repr -> "TkCharHash" <+> pretty (show c) <+> docText repr
    TkString s -> "TkString" <+> docText s
    TkStringHash s repr -> "TkStringHash" <+> docText s <+> docText repr
    TkOverloadedLabel label raw -> "TkOverloadedLabel" <+> docText label <+> docText raw
    TkSpecialLParen -> "TkSpecialLParen"
    TkSpecialRParen -> "TkSpecialRParen"
    TkSpecialUnboxedLParen -> "TkSpecialUnboxedLParen"
    TkSpecialUnboxedRParen -> "TkSpecialUnboxedRParen"
    TkSpecialComma -> "TkSpecialComma"
    TkSpecialSemicolon -> "TkSpecialSemicolon"
    TkSpecialLBracket -> "TkSpecialLBracket"
    TkSpecialRBracket -> "TkSpecialRBracket"
    TkSpecialBacktick -> "TkSpecialBacktick"
    TkSpecialLBrace -> "TkSpecialLBrace"
    TkSpecialRBrace -> "TkSpecialRBrace"
    TkMinusOperator -> "TkMinusOperator"
    TkPrefixMinus -> "TkPrefixMinus"
    TkPrefixBang -> "TkPrefixBang"
    TkPrefixTilde -> "TkPrefixTilde"
    TkRecordDot -> "TkRecordDot"
    TkPragma pragma' -> "TkPragma" <+> docPragmaType (pragmaType pragma')
    TkQuasiQuote quoter body -> "TkQuasiQuote" <+> docText quoter <+> docText body
    TkLineComment -> "TkLineComment"
    TkBlockComment -> "TkBlockComment"
    TkTHExpQuoteOpen -> "TkTHExpQuoteOpen"
    TkTHExpQuoteClose -> "TkTHExpQuoteClose"
    TkTHTypedQuoteOpen -> "TkTHTypedQuoteOpen"
    TkTHTypedQuoteClose -> "TkTHTypedQuoteClose"
    TkTHDeclQuoteOpen -> "TkTHDeclQuoteOpen"
    TkTHTypeQuoteOpen -> "TkTHTypeQuoteOpen"
    TkTHPatQuoteOpen -> "TkTHPatQuoteOpen"
    TkTHQuoteTick -> "TkTHQuoteTick"
    TkTHTypeQuoteTick -> "TkTHTypeQuoteTick"
    TkTHSplice -> "TkTHSplice"
    TkTHTypedSplice -> "TkTHTypedSplice"
    TkError msg -> "TkError" <+> docText msg
    TkEOF -> "TkEOF"

docTypeSyntaxForm :: TypeSyntaxForm -> Doc ann
docTypeSyntaxForm form =
  case form of
    TypeSyntaxExplicitNamespace -> "TypeSyntaxExplicitNamespace"
    TypeSyntaxInTerm -> "TypeSyntaxInTerm"

docNumericType :: NumericType -> Doc ann
docNumericType nt =
  case nt of
    TInteger -> "TInteger"
    TIntHash -> "TIntHash"
    TWordHash -> "TWordHash"
    TInt8Hash -> "TInt8Hash"
    TInt16Hash -> "TInt16Hash"
    TInt32Hash -> "TInt32Hash"
    TInt64Hash -> "TInt64Hash"
    TWord8Hash -> "TWord8Hash"
    TWord16Hash -> "TWord16Hash"
    TWord32Hash -> "TWord32Hash"
    TWord64Hash -> "TWord64Hash"

docFloatType :: FloatType -> Doc ann
docFloatType ft =
  case ft of
    TFractional -> "TFractional"
    TFloatHash -> "TFloatHash"
    TDoubleHash -> "TDoubleHash"

optionalField :: (a -> Doc ann) -> Maybe a -> [Doc ann]
optionalField f mVal =
  case mVal of
    Just val -> [f val]
    Nothing -> []

optionalField' :: (a -> Doc ann) -> Maybe a -> Doc ann
optionalField' f mVal =
  case mVal of
    Just val -> " " <> f val
    Nothing -> ""

listField :: (a -> Doc ann) -> [a] -> [Doc ann]
listField _ [] = []
listField f xs = [brackets (hsep (punctuate comma (map f xs)))]

boolField :: Bool -> [Doc ann]
boolField False = []
boolField True = ["True"]

docText :: Text -> Doc ann
docText = pretty . show

docName :: Name -> Doc ann
docName name =
  case nameQualifier name of
    Nothing -> docText (nameText name)
    Just qualifier -> docText qualifier <+> docText (nameText name)

docUnqualifiedName :: UnqualifiedName -> Doc ann
docUnqualifiedName name =
  "UnqualifiedName" <+> braces (docText (unqualifiedNameText name))

docUnqualifiedNameText :: UnqualifiedName -> Doc ann
docUnqualifiedNameText = docText . unqualifiedNameText

docTextList :: [Text] -> Doc ann
docTextList ts = brackets (hsep (punctuate comma (map docText ts)))

docTypeFamilyDecl :: TypeFamilyDecl -> Doc ann
docTypeFamilyDecl tf =
  "TypeFamilyDecl" <+> braces (hsep (punctuate comma fields))
  where
    fields =
      [ docTypeHeadForm (typeFamilyDeclHeadForm tf),
        if typeFamilyDeclExplicitFamilyKeyword tf then "True" else "False",
        docType (typeFamilyDeclHead tf)
      ]
        <> listField docTyVarBinder (typeFamilyDeclParams tf)
        <> optionalField docTypeFamilyResultSig (typeFamilyDeclResultSig tf)
        <> case typeFamilyDeclEquations tf of
          Nothing -> []
          Just eqs -> [brackets (hsep (punctuate comma (map docTypeFamilyEq eqs)))]

docTypeFamilyEq :: TypeFamilyEq -> Doc ann
docTypeFamilyEq eq =
  "TypeFamilyEq" <+> braces (hsep (punctuate comma fields))
  where
    fields =
      listField docTyVarBinder (typeFamilyEqForall eq)
        <> [docTypeHeadForm (typeFamilyEqHeadForm eq), docType (typeFamilyEqLhs eq), docType (typeFamilyEqRhs eq)]

docDataFamilyDecl :: DataFamilyDecl -> Doc ann
docDataFamilyDecl df =
  "DataFamilyDecl" <+> braces (hsep (punctuate comma fields))
  where
    fields =
      [docBinderHeadValue (dataFamilyDeclHead df)]
        <> optionalField docType (dataFamilyDeclKind df)

docBinderHead :: BinderHead UnqualifiedName -> [Doc ann]
docBinderHead head' =
  [docBinderHeadValue head']

docBinderHeadValue :: BinderHead UnqualifiedName -> Doc ann
docBinderHeadValue head' =
  case head' of
    PrefixBinderHead name params ->
      "Prefix" <+> hsep (docPrefixBinderHeadFields name params)
    InfixBinderHead lhs name rhs params ->
      "Infix" <+> hsep (docInfixBinderHeadFields lhs name rhs params)

docPrefixBinderHeadFields :: UnqualifiedName -> [TyVarBinder] -> [Doc ann]
docPrefixBinderHeadFields name params =
  docUnqualifiedNameText name : docBinderHeadParams params

docInfixBinderHeadFields :: TyVarBinder -> UnqualifiedName -> TyVarBinder -> [TyVarBinder] -> [Doc ann]
docInfixBinderHeadFields lhs name rhs params =
  [docTyVarBinderShort lhs, docUnqualifiedNameText name, docTyVarBinderShort rhs] <> docBinderHeadParams params

docBinderHeadParams :: [TyVarBinder] -> [Doc ann]
docBinderHeadParams [] = []
docBinderHeadParams params = [brackets (hsep (punctuate comma (map docTyVarBinder params)))]

docTyVarBinderShort :: TyVarBinder -> Doc ann
docTyVarBinderShort tvb
  | Nothing <- tyVarBinderKind tvb,
    TyVarBSpecified <- tyVarBinderSpecificity tvb,
    TyVarBVisible <- tyVarBinderVisibility tvb =
      docText (tyVarBinderName tvb)
  | otherwise = parens (docTyVarBinder tvb)

docTypeFamilyInst :: TypeFamilyInst -> Doc ann
docTypeFamilyInst tfi =
  "TypeFamilyInst" <+> braces (hsep (punctuate comma fields))
  where
    fields =
      listField docTyVarBinder (typeFamilyInstForall tfi)
        <> [docTypeHeadForm (typeFamilyInstHeadForm tfi), docType (typeFamilyInstLhs tfi), docType (typeFamilyInstRhs tfi)]

docTypeHeadForm :: TypeHeadForm -> Doc ann
docTypeHeadForm headForm =
  case headForm of
    TypeHeadPrefix -> "TypeHeadPrefix"
    TypeHeadInfix -> "TypeHeadInfix"

docDataFamilyInst :: DataFamilyInst -> Doc ann
docDataFamilyInst dfi =
  "DataFamilyInst" <+> braces (hsep (punctuate comma fields))
  where
    fields =
      boolField (dataFamilyInstIsNewtype dfi)
        <> listField docTyVarBinder (dataFamilyInstForall dfi)
        <> [docType (dataFamilyInstHead dfi)]
        <> optionalField docType (dataFamilyInstKind dfi)
        <> listField docDataConDecl (dataFamilyInstConstructors dfi)
        <> listField docDerivingClause (dataFamilyInstDeriving dfi)