fixplate-0.1.7: Data/Generics/Fixplate/Pretty.hs
-- | Generic pretty-printing of expression trees.
--
-- TODO:
--
-- * make the style configurable (so that we can print the same expression in different formats)
-- (in Haskell98, we have to use the record instead of class trick for this?)
--
-- * corresponding parser?
--
module Data.Generics.Fixplate.Pretty where
--------------------------------------------------------------------------------
import Prelude
import Data.List ( intersperse )
import Data.Generics.Fixplate
import Data.Foldable
import Text.Show ()
--------------------------------------------------------------------------------
-- | Associativity
data Assoc
= NoAssoc
| LeftAssoc
| RightAssoc
deriving (Eq,Show)
-- | A pair of matching brackets, eg. @Bracket \"(\" \")\"@ or @Bracket \"[|\" \"|]\"@.
data Bracket = Bracket !String !String deriving (Eq,Show)
-- | A separator, eg. @\",\"@ or @\" | \"@.
type Separator = String
-- | Application style
data AppStyle
= Haskell -- ^ eg. @(Node arg1 arg2 arg3)@; precedence will be @app_prec == 10@
| Algol !Bracket !Separator -- ^ eg. @node[arg1,arg2,arg3]@; precedence will be 11, but child environment precedence will be 0
deriving (Eq,Show)
-- | Mixfix style. Example:
--
-- > [ Keyword "if" , Placeholder , keyword "then" , Placeholder , keyword "else" , Placeholder ]
--
data MixWord
= Keyword String
| Placeholder
deriving (Eq,Show)
mixWords :: [MixWord] -> [ShowS] -> ShowS
mixWords mws args = Prelude.foldr (.) id (intersperse (showChar ' ') (go mws args)) where
go :: [MixWord] -> [ShowS] -> [ShowS]
go (Keyword s : rest) fs = showString s : go rest fs
go (Placeholder : rest) (f:fs) = f : go rest fs
go (Placeholder : rest) [] = error "mixWords: not enough arguments"
go [] [] = []
go [] (f:fs) = f : go [] fs
-- | Fixities. TODO: separate non-fixity stuff like style and words
data Fixity
= Atom -- ^ eg. @variable@; precedence will be 666
| Application !AppStyle -- ^ eg. @(Node arg1 arg2 arg3)@ or @node[arg1,arg2,arg3]@.
| Prefix !Int -- ^ eg. @~arg@; the @Int@ is the precendence
| Infix !Assoc !Int -- ^ eg. @x+y@
| Postfix !Int -- ^ eg. @arg++@
| Mixfix [MixWord] -- ^ eg. @if ... then ... else ... @ or @let ... in ...@. With precedence 0?
| Custom !Int -- ^ for your custom rendering
deriving (Eq,Show)
fixityPrecedence :: Fixity -> Int
fixityPrecedence f = case f of
Atom -> 666
Application style ->
case style of
Haskell -> 10
Algol {} -> 11
Prefix prec -> prec
Infix assoc prec -> prec
Postfix prec -> prec
Mixfix {} -> 0
Custom prec -> prec
--------------------------------------------------------------------------------
-- | A class encoding fixity and rendering of nodes if the tree.
--
-- Minimum complete definition: 'fixity', and 'showNode' or 'showsPrecNode'.
-- Unless you want some type of rendering not directly supported, you shouldn't specify 'showsPrecNode'.
--
class (Functor f, Foldable f) => Pretty f where
-- | fixity of the node
fixity :: f a -> Fixity
-- | a string representing the node /without/ the children
showNode :: f a -> String
-- | full rendering of the node. You can redefine this for custom renderings.
showsPrecNode :: (Int -> a -> ShowS) -> Int -> f a -> ShowS
showsPrecNode child d node = showParen (d > prec) $
case fty of
Atom -> showString (showNode node)
Application style -> case style of
Haskell -> head . args where
head = showString (showNode node)
args = Prelude.foldr (.) id [ showChar ' ' . child (prec+1) c | c <- children ]
Algol (Bracket open close) sep -> head . showString open . args . showString close where
head = showString (showNode node)
args = Prelude.foldr (.) id
$ intersperse (showString sep) [ child 0 c | c <- children ]
Prefix prec ->
case children of
[] -> error "showsPrecNode: prefix node with no arguments"
(c:cs) -> op . arg1 c . args cs
where
op = showString (showNode node)
arg1 c = child (prec+1) c
args cs = Prelude.foldr (.) id [ showChar ' ' . child (prec+1) c | c <- cs ]
Postfix prec ->
case children of
[] -> error "showsPrecNode: postfix node with no arguments"
ccs -> let (cs,c) = (Prelude.init ccs, Prelude.last ccs)
in args cs . arg1 c . op
where
op = showString (showNode node)
arg1 c = child (prec+1) c
args cs = Prelude.foldr (.) id [ child (prec+1) c . showChar ' ' | c <- cs ]
Infix assoc prec ->
case children of
[] -> error "showsPrecNode: infix node with no arguments"
[_] -> error "showsPrecNode: infix node with a single argument"
(c1:c2:cs) -> lhs c1 . op . rhs c2 . rest cs
where
lhs c1 = child lprec c1
op = showString (showNode node)
rhs c2 = child rprec c2
rest cs = Prelude.foldr (.) id [ showChar ' ' . child (prec+1) c | c <- cs ]
lprec = case assoc of { LeftAssoc -> prec ; _ -> prec+1 }
rprec = case assoc of { RightAssoc -> prec ; _ -> prec+1 }
Mixfix mwords -> mixWords mwords [ child (prec+1) {- ? -} c | c <- children ]
Custom prec -> error "for custom rendering, you should redefine `showsPrecNode'"
where
fty = fixity node
prec = fixityPrecedence fty
children = toList node
--------------------------------------------------------------------------------
-- | Render the expression
pretty :: Pretty f => Mu f -> String
pretty tree = prettyS tree ""
prettyS :: Pretty f => Mu f -> ShowS
prettyS = prettyPrec 0
prettyPrec :: Pretty f => Int -> Mu f -> ShowS
prettyPrec d t = go d t where
go d (Fix t) = showsPrecNode go d t
--------------------------------------------------------------------------------