cuddle-1.8.0.0: src/Codec/CBOR/Cuddle/Pretty.hs
{-# LANGUAGE DerivingStrategies #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE OverloadedLabels #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TypeData #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE UndecidableInstances #-}
{-# LANGUAGE ViewPatterns #-}
{-# OPTIONS_GHC -Wno-orphans #-}
module Codec.CBOR.Cuddle.Pretty (
CommentRender (..),
PrettyStage,
XXTopLevel (..),
XXType2 (..),
XTerm (..),
XCddl (..),
XRule (..),
renderCDDL,
) where
import Codec.CBOR.Cuddle.CDDL
import Codec.CBOR.Cuddle.CDDL.CTree (CTree, PTerm (..), XXCTree)
import Codec.CBOR.Cuddle.CDDL.CTree qualified as CT
import Codec.CBOR.Cuddle.CDDL.CtlOp (CtlOp)
import Codec.CBOR.Cuddle.Comments (CollectComments (..), Comment, HasComment (..), unComment)
import Codec.CBOR.Cuddle.Pretty.Columnar (
Cell (..),
CellAlign (..),
Columnar (..),
Row (..),
cellL,
columnarListing,
columnarSepBy,
emptyCell,
prettyColumnar,
singletonRow,
)
import Codec.CBOR.Cuddle.Pretty.Utils (renderedLen, softspace)
import Data.ByteString.Base16 qualified as B16
import Data.ByteString.Char8 qualified as BS
import Data.Default.Class (Default)
import Data.Foldable (Foldable (..))
import Data.List.NonEmpty qualified as NE
import Data.String (IsString, fromString)
import Data.Text (Text)
import Data.Text qualified as T
import Data.Void (Void, absurd)
import GHC.Generics (Generic)
import Optics.Core ((^.))
import Prettyprinter
import Prettyprinter.Render.Text qualified as PT
type data PrettyStage
newtype instance XXTopLevel PrettyStage = PrettyXXTopLevel Comment
deriving (Generic, CollectComments, Show, Eq)
newtype instance XXType2 PrettyStage = PrettyXXType2 Void
deriving (Generic, CollectComments, Show, Eq)
newtype instance XTerm PrettyStage = PrettyXTerm {unPrettyXTerm :: Comment}
deriving (Generic, CollectComments, Semigroup, Monoid, IsString, Show, Eq)
newtype instance XCddl PrettyStage = PrettyXCddl [Comment]
deriving (Generic, CollectComments, Show, Eq)
newtype instance XRule PrettyStage = PrettyXRule {unPrettyXRule :: Comment}
deriving (Generic, CollectComments, Show, Eq)
deriving newtype (Default)
instance HasComment (XTerm PrettyStage) where
commentL = #unPrettyXTerm
instance HasComment (XRule PrettyStage) where
commentL = #unPrettyXRule
instance Pretty (CDDL PrettyStage) where
pretty = vsep . fmap pretty . NE.toList . cddlTopLevel
instance Pretty (TopLevel PrettyStage) where
pretty (XXTopLevel (PrettyXXTopLevel cmt)) = pretty cmt
pretty (TopLevelRule x) = pretty x <> hardline
instance Pretty Name where
pretty (Name name) = pretty name
data CommentRender
= PreComment
| PostComment
prettyCommentNoBreak :: Comment -> Doc ann
prettyCommentNoBreak = align . vsep . toList . fmap (pretty . (";" <>)) . unComment
prettyCommentNoBreakWS :: Comment -> Doc ann
prettyCommentNoBreakWS cmt
| cmt == mempty = mempty
| otherwise = space <> prettyCommentNoBreak cmt
instance Pretty Comment where
pretty c
| c == mempty = mempty
| otherwise = prettyCommentNoBreak c <> hardline
type0Def :: Type0 PrettyStage -> Doc ann
type0Def t = nest 2 $ line' <> pretty t
instance Pretty (Rule PrettyStage) where
pretty (Rule n mgen assign tog cmt) =
pretty (cmt ^. commentL)
<> groupIfNoComments
tog
( pretty n <> pretty mgen <+> case tog of
TOGType t -> ppAssignT <> softline <> type0Def t
TOGGroup g -> ppAssignG <> softline <> nest 2 (line' <> pretty g)
)
where
ppAssignT = case assign of
AssignEq -> "="
AssignExt -> "/="
ppAssignG = case assign of
AssignEq -> "="
AssignExt -> "//="
instance Pretty (GenericArg PrettyStage) where
pretty (GenericArg l) =
"<" <> concatWith (\x y -> x <> "," <+> y) (pretty <$> NE.toList l) <> ">"
instance Pretty (GenericParameter PrettyStage) where
pretty (GenericParameter n (PrettyXTerm c)) = pretty n <> prettyCommentNoBreakWS c
instance Pretty (GenericParameters PrettyStage) where
pretty (GenericParameters l) =
"<" <> concatWith (\x y -> x <> "," <+> y) (pretty <$> NE.toList l) <> ">"
instance Pretty (Type0 PrettyStage) where
pretty t0@(Type0 (NE.toList -> l)) =
groupIfNoComments t0 $ columnarSepBy "/" . Columnar $ type1ToRow <$> l
where
type1ToRow (Type1 t2 tyOp (PrettyXTerm cmt)) =
let
valCell = case tyOp of
Nothing -> cellL t2
Just (to, t2') -> Cell (pretty t2 <+> pretty to <+> pretty t2') LeftAlign
in
Row [valCell, Cell (prettyCommentNoBreakWS cmt) LeftAlign]
instance Pretty CtlOp where
pretty = pretty . T.toLower . T.pack . show
instance Pretty TyOp where
pretty (RangeOp ClOpen) = "..."
pretty (RangeOp Closed) = ".."
pretty (CtrlOp n) = "." <> pretty n
instance Pretty (Type1 PrettyStage) where
pretty (Type1 t2 Nothing (PrettyXTerm cmt)) = groupIfNoComments t2 (pretty t2) <> prettyCommentNoBreakWS cmt
pretty (Type1 t2 (Just (tyop, t2')) (PrettyXTerm cmt)) =
groupIfNoComments t2 (pretty t2)
<+> pretty tyop
<+> groupIfNoComments t2' (pretty t2')
<> prettyCommentNoBreakWS cmt
instance Pretty (Type2 PrettyStage) where
pretty (T2Value v) = pretty v
pretty (T2Name n mg) = pretty n <> pretty mg
pretty (T2Group g) = cEncloseSep "(" ")" mempty [pretty g]
pretty (T2Map g) = prettyGroup AsMap g
pretty (T2Array g) = prettyGroup AsArray g
pretty (T2Unwrapped n mg) = "~" <+> pretty n <> pretty mg
pretty (T2Enum g) = "&" <+> prettyGroup AsGroup g
pretty (T2EnumRef g mg) = "&" <+> pretty g <> pretty mg
pretty (T2Tag minor t) = "#6" <> min' <> enclose "(" ")" (group $ nest 2 (line' <> pretty t) <> line')
where
min' = case minor of
Nothing -> mempty
Just m -> "." <> pretty m
pretty (T2DataItem major mminor) =
"#" <> pretty major <> case mminor of
Nothing -> mempty
Just minor -> "." <> pretty minor
pretty T2Any = "#"
pretty (XXType2 (PrettyXXType2 v)) = absurd v
instance Pretty OccurrenceIndicator where
pretty OIOptional = "?"
pretty OIZeroOrMore = "*"
pretty OIOneOrMore = "+"
pretty (OIBounded ml mh) = pretty ml <> "*" <> pretty mh
-- | Control how to render a group
data GroupRender
= AsMap
| AsArray
| AsGroup
memberKeySep :: MemberKey i -> Doc ann
memberKeySep MKType {} = " => "
memberKeySep _ = " : "
cEncloseSep :: Doc ann -> Doc ann -> Doc ann -> [Doc ann] -> Doc ann
cEncloseSep lEnc rEnc _ [] = lEnc <> rEnc
cEncloseSep lEnc rEnc _ [x] =
group . align . vcat $
[ lEnc <> softspace <> x
, rEnc
]
cEncloseSep lEnc rEnc s (h : tl) =
group . align $ vsep ((lEnc <> lSpaces <> group h) : fmap ((s <> space) <>) tl) <> line' <> rEnc
where
lSpaces = mconcat $ replicate (renderedLen s) softspace
groupIfNoComments :: CollectComments a => a -> Doc ann -> Doc ann
groupIfNoComments x
| not (any (mempty /=) $ collectComments x) = group
| otherwise = id
columnarGroupChoice :: GrpChoice PrettyStage -> Columnar ann
columnarGroupChoice (GrpChoice ges _cmt) = Columnar grpEntryRows
where
groupEntryRow (GroupEntry oi gev (PrettyXTerm cmt)) =
Row $
[maybe emptyCell (\x -> Cell (pretty x <> space) LeftAlign) oi]
<> groupEntryVariantCells gev
<> [Cell (prettyCommentNoBreakWS cmt) LeftAlign]
groupEntryVariantCells (GERef n ga) = [Cell (pretty n <> pretty ga) LeftAlign]
groupEntryVariantCells (GEType (Just mk) t0) = [cellL mk, Cell (memberKeySep mk <> groupIfNoComments t0 (align $ pretty t0)) LeftAlign]
groupEntryVariantCells (GEType Nothing t0) = [cellL t0]
groupEntryVariantCells (GEGroup g) = [Cell (prettyGroup AsGroup g) LeftAlign, emptyCell]
grpEntryRows = groupEntryRow <$> ges
prettyGroup :: GroupRender -> Group PrettyStage -> Doc ann
prettyGroup gr g@(Group (toList -> xs)) =
groupIfNoComments g . columnarListing (lEnc <> softspace) rEnc "// " . Columnar $
(\x -> singletonRow . groupIfNoComments x . columnarSepBy "," $ columnarGroupChoice x) <$> xs
where
(lEnc, rEnc) = case gr of
AsMap -> ("{", "}")
AsArray -> ("[", "]")
AsGroup -> ("(", ")")
instance Pretty (GroupEntry PrettyStage) where
pretty ge = prettyColumnar . columnarGroupChoice $ GrpChoice [ge] mempty
instance Pretty (MemberKey PrettyStage) where
pretty (MKType t1) = pretty t1
pretty (MKBareword n) = pretty n
pretty (MKValue v) = pretty v
instance Pretty Value where
pretty (Value v cmt) = pretty v <> prettyCommentNoBreakWS cmt
instance Pretty ValueVariant where
pretty (VUInt i) = pretty i
pretty (VNInt i) = "-" <> pretty i
pretty (VBignum i) = pretty i
pretty (VFloat16 i) = pretty i
pretty (VFloat32 i) = pretty i
pretty (VFloat64 i) = pretty i
pretty (VText t) = enclose "\"" "\"" $ pretty t
pretty (VBytes b) = fromString $ "h" <> "'" <> BS.unpack (B16.encode b) <> "'"
pretty (VBool True) = "true"
pretty (VBool False) = "false"
instance Pretty PTerm where
pretty = \case
PTBool -> "bool"
PTUInt -> "uint"
PTNInt -> "nint"
PTInt -> "int"
PTHalf -> "half"
PTFloat -> "float"
PTDouble -> "double"
PTBytes -> "bytes"
PTText -> "text"
PTAny -> "any"
PTNil -> "nil"
PTUndefined -> "undefined"
-- | Render a pretty-stage CDDL to 'Text', removing trailing whitespace.
renderCDDL :: LayoutOptions -> CDDL PrettyStage -> Text
renderCDDL opts =
PT.renderStrict . removeTrailingWhitespace . layoutPretty opts . pretty
instance Pretty (XXCTree p) => Pretty (CTree p) where
pretty = \case
CT.Literal v -> pretty v
CT.Postlude v -> pretty v
CT.Range lo hi ClOpen -> pretty lo <+> "..." <+> pretty hi
CT.Range lo hi Closed -> pretty lo <+> ".." <+> pretty hi
CT.KV k v _ -> pretty k <+> "==>" <+> pretty v
CT.CTreeE x -> pretty x
CT.Occur v oi ->
case oi of
OIOptional -> "?" <+> pretty v
OIZeroOrMore -> "*" <+> pretty v
OIOneOrMore -> "+" <+> pretty v
OIBounded lo hi -> foldMap pretty lo <> "*" <> foldMap pretty hi <+> pretty v
CT.Map m -> encloseSep "{" "}" "," $ pretty <$> m
CT.Array e -> list $ pretty <$> e
CT.Choice c -> group . vsep . punctuate "//" $ pretty <$> toList c
CT.Group g -> tupled $ pretty <$> g
CT.Control op tgt ctl -> pretty tgt <+> pretty op <+> pretty ctl
CT.Enum e -> "&" <> pretty e
CT.Unwrap x -> "~" <> pretty x
CT.Tag t x -> "#6." <> pretty t <> "(" <> pretty x <> ")"