prettyprinter-0.1: src/Data/Text/Prettyprint/Doc/Render/Util/SimpleDocTree.hs
{-# LANGUAGE CPP #-}
{-# LANGUAGE LambdaCase #-}
#include "version-compatibility-macros.h"
-- | Conversion of the linked-list-like 'SimpleDoc' to a tree-like
-- 'SimpleDocTree'.
module Data.Text.Prettyprint.Doc.Render.Util.SimpleDocTree (
SimpleDocTree(..),
treeForm,
unAnnotateST,
reAnnotateST,
) where
import Control.Applicative
import Data.Text (Text)
import Data.Text.Prettyprint.Doc
import Data.Text.Prettyprint.Doc.Render.Util.Panic
#if MONAD_FAIL
import Control.Monad.Fail
#endif
-- | A type for parsers of unique results. Token stream »s«, results »a«.
--
-- Hand-written to avoid a dependency on a parser lib.
newtype UniqueParser s a = UniqueParser { runParser :: s -> Maybe (a, s) }
instance Functor (UniqueParser s) where
fmap f (UniqueParser mx) = UniqueParser (\s ->
fmap (\(x,s') -> (f x, s')) (mx s))
instance Applicative (UniqueParser s) where
pure x = UniqueParser (\rest -> Just (x, rest))
UniqueParser mf <*> UniqueParser mx = UniqueParser (\s -> do
(f, s') <- mf s
(x, s'') <- mx s'
pure (f x, s'') )
instance Monad (UniqueParser s) where
#if !(APPLICATIVE_MONAD)
return = pure
#endif
UniqueParser p >>= f = UniqueParser (\s -> do
(a', s') <- p s
(a'', s'') <- runParser (f a') s'
pure (a'', s'') )
fail _err = empty
#if MONAD_FAIL
instance MonadFail (UniqueParser s) where
fail _err = empty
#endif
instance Alternative (UniqueParser s) where
empty = UniqueParser (const empty)
UniqueParser p <|> UniqueParser q = UniqueParser (\s -> p s <|> q s)
data SimpleDocTok ann
= TokEmpty
| TokChar Char
| TokText !Int Text
| TokLine Int
| TokAnnPush ann
| TokAnnPop
deriving (Eq, Ord, Show)
-- | A 'SimpleDoc' is a linked list of different annotated cons cells ('SText'
-- and then some further 'SimpleDoc', 'SLine' and then some further 'SimpleDoc', …).
-- This format is very suitable as a target for a layout engine, but not very
-- useful for rendering to a structured format such as HTML, where we don’t want
-- to do a lookahead until the end of some markup. These formats benefit from a
-- tree-like structure that explicitly marks its contents as annotated.
-- 'SimpleDocTree' is that format.
data SimpleDocTree ann
= STEmpty
| STChar Char
| STText !Int Text
| STLine !Int
| STAnn ann (SimpleDocTree ann)
| STConcat [SimpleDocTree ann]
deriving (Eq, Ord, Show)
-- | Get the next token, consuming it in the process.
nextToken :: UniqueParser (SimpleDoc ann) (SimpleDocTok ann)
nextToken = UniqueParser (\case
SFail -> panicUncaughtFail
SEmpty -> empty
SChar c rest -> Just (TokChar c , rest)
SText l t rest -> Just (TokText l t , rest)
SLine i rest -> Just (TokLine i , rest)
SAnnPush ann rest -> Just (TokAnnPush ann , rest)
SAnnPop rest -> Just (TokAnnPop , rest) )
sdocToTreeParser :: UniqueParser (SimpleDoc ann) (SimpleDocTree ann)
sdocToTreeParser = fmap wrap (many contentPiece)
where
wrap :: [SimpleDocTree ann] -> SimpleDocTree ann
wrap = \case
[] -> STEmpty
[x] -> x
xs -> STConcat xs
contentPiece = nextToken >>= \case
TokEmpty -> pure STEmpty
TokChar c -> pure (STChar c)
TokText l t -> pure (STText l t)
TokLine i -> pure (STLine i)
TokAnnPop -> empty
TokAnnPush ann -> do annotatedContents <- sdocToTreeParser
TokAnnPop <- nextToken
pure (STAnn ann annotatedContents)
-- | Convert a 'SimpleDoc' to its 'SimpleDocTree' representation.
treeForm :: SimpleDoc ann -> SimpleDocTree ann
treeForm sdoc = case runParser sdocToTreeParser sdoc of
Nothing -> panicSimpleDocTreeConversionFailed
Just (sdoct, SEmpty) -> sdoct
Just (_, _unconsumed) -> panicInputNotFullyConsumed
-- $
--
-- >>> :set -XOverloadedStrings
-- >>> treeForm (layoutPretty defaultLayoutOptions ("lorem" <+> "ipsum" <+> annotate True ("TRUE" <+> annotate False "FALSE") <+> "dolor"))
-- STConcat [STText 5 "lorem",STChar ' ',STText 5 "ipsum",STChar ' ',STAnn True (STConcat [STText 4 "TRUE",STChar ' ',STAnn False (STText 5 "FALSE")]),STChar ' ',STText 5 "dolor"]
-- | Remove all annotations. 'unAnnotate' for 'SimpleDocTree'.
unAnnotateST :: SimpleDocTree ann -> SimpleDocTree xxx
unAnnotateST = \case
STEmpty -> STEmpty
STChar c -> STChar c
STText l t -> STText l t
STLine i -> STLine i
STAnn _ rest -> unAnnotateST rest
STConcat xs -> STConcat (map unAnnotateST xs)
-- | Change the annotation of a document. 'reAnnotate' for 'SimpleDocTree'.
reAnnotateST :: (ann -> ann') -> SimpleDocTree ann -> SimpleDocTree ann'
reAnnotateST f = go
where
go = \case
STEmpty -> STEmpty
STChar c -> STChar c
STText l t -> STText l t
STLine i -> STLine i
STAnn ann rest -> STAnn (f ann) (go rest)
STConcat xs -> STConcat (map go xs)