tilia-0.0.1.0: src/Tilia/Render/Name.hs
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE ViewPatterns #-}
-- | Names, and the decorations the author put around them.
module Tilia.Render.Name
( -- * Rendering anything GHC can show
outputable,
showGhc,
sourceText,
-- * Names
name,
moduleHeadName,
wrappedName,
namespaceSpec,
multiplicity,
-- * Definition heads
defHead,
)
where
import Data.Text (Text)
import Data.Text qualified as T
import GHC.Hs
import GHC.LanguageExtensions.Type (Extension (..))
import GHC.Types.Name.Occurrence (OccName, occNameString)
import GHC.Types.Name.Reader
import GHC.Types.SourceText
import GHC.Types.SrcLoc (getLoc)
import GHC.Utils.Outputable (Outputable, ppr, showSDocUnsafe)
import Tilia.Doc.Combinators
import Tilia.Render.Context
import Tilia.Span
import Tilia.Span.Ghc
-- | Anything GHC knows how to show.
--
-- For the leaves that have no structure worth walking—numeric literals,
-- occurrence names, calling conventions. Never for anything that might need
-- a line break inside it, since the result is emitted as one fragment.
outputable :: (Outputable a) => a -> Doc
outputable = txt . showGhc
-- | The text GHC would show for something.
showGhc :: (Outputable a) => a -> Text
showGhc = T.pack . showSDocUnsafe . ppr
-- | The text the author wrote, when GHC kept it.
sourceText :: SourceText -> Doc
sourceText = \case
NoSourceText -> mempty
SourceText s -> outputable s
----------------------------------------------------------------------------
-- Names
-- | A name, with whatever the author wrapped it in.
name :: Ctx -> LocatedN RdrName -> Doc
name ctx l = at ctx l $ \x -> adorn ctx (spanOf l) x (getLoc l) (bareName x)
-- | The name itself, with nothing around it.
bareName :: RdrName -> Doc
bareName = \case
Unqual occName -> outputable occName
Qual mname occName -> qualifiedName mname occName
Orig _ occName -> outputable occName
Exact n -> outputable n
-- | Put back whatever brackets, backticks or ticks the author used.
adorn :: Ctx -> Maybe Span -> RdrName -> EpAnn NameAnn -> Doc -> Doc
adorn ctx here x = go
where
go EpAnn {anns} = case anns of
-- A promotion tick, with whatever the name carries under it.
NameAnnQuote {nann_quoted} -> (txt "'" <>) . go nann_quoted
-- The empty unboxed sum and the empty list are written out whole:
-- there is no name under the brackets to print.
NameAnnOnly {nann_adornment = NameParensHash {}} -> const (txt "(# #)")
NameAnnOnly {nann_adornment = NameSquare {}} ->
const (txt "[" <> insideBrackets here mempty <> txt "]")
-- @->@ is the one name that is a keyword as well, and the parentheses
-- are recorded on their own rather than as an adornment.
NameAnnRArrow {nann_mopen = Just _} -> inParens
-- The name inside the brackets is claimed separately from the
-- brackets themselves, so that a comment written against it—@( {-
-- here -} :+: )@—is put where it was written rather than after the
-- closing bracket.
NameAnn {nann_adornment, nann_name} -> case nann_adornment of
NameParens {} -> inParens . spaceOutHash . itsOwn nann_name
NameBackquotes {} -> backticks . itsOwn nann_name
_ -> itsOwn nann_name
_ -> id
itsOwn = atSpan ctx . annSpan
inParens d = txt "(" <> d <> txt ")"
-- With UnboxedSums on, @(#@ lexes as one token, so an operator starting
-- with @#@ cannot sit against its opening bracket.
spaceOutHash d
| extensionOn ctx UnboxedSums,
-- A qualified name never begins with a @#@.
Unqual (occNameString -> '#' : _) <- x =
space <> d <> space
| otherwise = d
-- | A name written with its module.
qualifiedName :: ModuleName -> OccName -> Doc
qualifiedName mname occName = outputable mname <> txt "." <> outputable occName
-- | The name in a module header, with the keyword that introduces it.
moduleHeadName :: Ctx -> ModuleName -> Doc
moduleHeadName ctx mname =
txt keyword <> space <> outputable mname
where
keyword = case ctxSourceType ctx of
ModuleSource -> "module"
SignatureSource -> "signature"
-- | A name as it appears in an import or export list.
wrappedName :: Ctx -> IEWrappedName GhcPs -> Doc
wrappedName ctx = \case
IEName _ x -> name ctx x
IEDefault _ x -> keyed "default" x
IEPattern _ x -> keyed "pattern" x
IEType _ x -> keyed "type" x
IEData _ x -> keyed "data" x
where
keyed kw x = txt kw <> space <> name ctx x
-- | The @type@ or @data@ that disambiguates which namespace is meant.
namespaceSpec :: NamespaceSpecifier -> Doc
namespaceSpec = \case
NoNamespaceSpecifier -> mempty
TypeNamespaceSpecifier _ -> txt "type" <> space
DataNamespaceSpecifier _ -> txt "data" <> space
-- | A multiplicity annotation on an arrow or a field.
multiplicity :: (mult -> Doc) -> HsMultAnnOf mult GhcPs -> Doc
multiplicity render = \case
HsUnannotated _ -> mempty
HsLinearAnn _ -> txt "%1"
HsExplicitMult _ mult -> txt "%" <> render mult
----------------------------------------------------------------------------
-- Definition heads
-- | The left-hand side of a definition: a name and the things it is applied
-- to.
--
-- Written infix, the first two arguments straddle the name and any further
-- ones force the whole of that into parentheses, which is the only way the
-- source could have been written. Written prefix, the arguments simply
-- follow. The indentation flag is for the callers whose body is going to be
-- indented anyway, so that the arguments do not end up two steps in.
defHead ::
-- | Written infix?
Bool ->
-- | Indent the arguments?
Bool ->
-- | The name
Doc ->
-- | The arguments
[Doc] ->
Doc
defHead True indentArgs nameDoc (a0 : a1 : rest) =
wrap (a0 <> breakOrSpace <> indent (align (nameDoc <> space <> a1)))
<> includeUnless (null rest) (nest (steps indentArgs) (breakOrSpace <> spread rest))
where
wrap = if null rest then id else parens
defHead _ indentArgs nameDoc args =
nameDoc
<> includeUnless (null args) (nest (steps indentArgs) (breakOrSpace <> spread args))
-- | Arguments, each aligned under itself, one per line when broken.
spread :: [Doc] -> Doc
spread = align . sepBy breakOrSpace . map align
steps :: Bool -> Int
steps b = if b then 1 else 0