{-# LANGUAGE DefaultSignatures, FlexibleInstances #-} {-# LANGUAGE GeneralizedNewtypeDeriving, KindSignatures, LambdaCase, MultiWayIf #-} {-# LANGUAGE TemplateHaskell, UnicodeSyntax #-} module Printcess.Core where import Control.Monad.State.Strict import Control.Lens import qualified Data.List.NonEmpty as NE import Data.List.NonEmpty (NonEmpty(..)) import Printcess.Config -- Associativity --------------------------------------------------------------- data Assoc = AssocN | AssocL | AssocR deriving (Eq, Ord, Read, Show) -- Pretty Printing State ------------------------------------------------------- data PrettySt = PrettySt { _indentation :: Int , _precedence :: Int , _assoc :: Assoc , _maxLineWidth :: Maybe Int , _text :: NE.NonEmpty String , _indentChar :: Char , _indentDepth :: Int , _indentAfterBreaks :: Int } stFromConfig :: Config → PrettySt stFromConfig c = PrettySt { _indentation = _configInitIndent c , _precedence = _configInitPrecedence c , _assoc = AssocN , _maxLineWidth = _configMaxLineWidth c , _text = "" :| [] , _indentChar = _configIndentChar c , _indentDepth = _configIndentDepth c , _indentAfterBreaks = _configIndentAfterBreaks c } makeLenses ''PrettySt -- Pretty Printing ------------------------------------------------------------- -- | Render a 'Pretty' printable @a@ to 'String' using a 'Config', that -- specifies how the @a@ should be rendered. For example -- -- > pretty defConfig (1 :: Int) -- evaluates to "1" pretty :: Pretty a => State Config () -- ^ Updates for the default pretty printing 'Config'. -> a -- ^ A 'Pretty' printable @a@. -> String -- ^ The pretty printed @a@. pretty c = concat . (`sepByList` "\n") . reverse . NE.toList . view text . (`execState` stFromConfig (execState c def)) . runPrettyM . pp -- | Render a 'Pretty' printable @a@ to @stdout@ using a 'Config', that -- specifies how the @a@ should be rendered. -- -- Convenience function, defined as: -- -- > prettyPrint c = liftIO . putStrLn . pretty c prettyPrint :: (MonadIO m, Pretty a) => State Config () -- ^ Updates for the default pretty printing 'Config'. -> a -- ^ A 'Pretty' printable @a@. -> m () -- ^ An 'IO' action pretty printing the @a@ to @stdout@. prettyPrint c = liftIO . putStrLn . pretty c -- Type Classes ---------------------------------------------------------------- -- | Instanciating this class for a type, declares how values of that type -- should be pretty printed. -- -- As pretty printing may depend on some context, e.g. the current indentation -- level, a 'State' monad for pretty printing ('PrettyM') is used. -- -- A default implementation is provided copying behavior from a 'Show' instance. -- This can be convenient for deriving 'Pretty', e.g. for base types or -- debugging. The default implementation is defined by @pp = pp . show@. class Pretty a where -- | Pretty print an @a@ as a 'PrettyM' action. pp :: a → PrettyM () default pp :: Show a => a → PrettyM (); pp = pp . show head1L :: Lens' (NE.NonEmpty a) a head1L = lens NE.head (\(_ :| xs) x → x :| xs) tail1L :: Lens' (NE.NonEmpty a) [a] tail1L = lens NE.tail (\(x :| _) xs → x :| xs) charsBeforeWord :: Int -> (Char -> Bool) -> String -> Int charsBeforeWord nWords0 cIndent s0 = go s0 nWords0 where go s n = length sIndent + if n == 0 then 0 else length sWord + go sAfterWord (n-1) where (sIndent, sBeforeWord) = break (not . cIndent) s (sWord, sAfterWord) = break cIndent sBeforeWord charsBeforeWordM :: Int -> PrettyM Int charsBeforeWordM n0 = do cIndent ← use indentChar curText ← use $ text . head1L pure $ charsBeforeWord n0 (`elem` [' ', '\t', cIndent]) curText -- Isomorphic lines and unlines implementations lines' :: String → [String] lines' = go "" where go s = \case "" → [s] c:cs | c == '\n' → s : go "" cs | otherwise → go (s++[c]) cs unlines' :: [String] → String unlines' = \case [] → "" [x] → x x:xs → x ++ '\n' : unlines' xs isWS, isNoWS :: Char → Bool isWS = (`elem` [' ', '\t']) isNoWS = not . isWS dropWhileEnd :: (a → Bool) → [a] → [a] dropWhileEnd f = reverse . dropWhile f . reverse -- | In contrast to 'Show', @"foo"@ is printed as @"foo"@ and not @"\\"foo\\""@. -- Most of the other instances are defined in terms of this instance. -- If the 'String' contains newline characters (@'\n'@), indentation is inserted -- automatically afterwards. -- If the current line gets too long, it is automatically broken. instance Pretty String where pp = go . lines' where go :: [String] -> PrettyM () go [] = pure () go [s] = ppLine True s go (s:ss) = do ppLine True s; text %= ("" NE.<|); go ss ppLine :: Bool -> String -> PrettyM () ppLine first s = do oldLine ← use $ text . head1L when (null oldLine) addIndent text . head1L %= (++s) curLine ← use $ text . head1L -- We have to allow at least indentation + 1 word, otherwise indenting after -- line break due to line continuation can cause infinite loop mMaxLineLength ← use maxLineWidth forM_ mMaxLineLength $ \maxLineLength → do maxLineLength' ← max <$> charsBeforeWordM 1 <*> pure maxLineLength when (length curLine > maxLineLength') $ do let (curLine', lineRest) = splitAt maxLineLength curLine -- length s1 == maxLineLength let (wordRest, curLine'') | isNoWS (head lineRest) = both %~ reverse $ break (==' ') $ reverse curLine' | otherwise = ("", curLine') text . head1L .= dropWhileEnd isWS curLine'' -- Increase indentation once after the first forced line break, this results into: -- this line was too long -- still the first line -- it won't stop i ← use indentAfterBreaks let f | first = indentedByChars i | otherwise = id f $ do text %= ("" NE.<|); ppLine False $ dropWhile isWS $ wordRest ++ lineRest -- | In contrast to 'Show', @\'c\'@ is printed as @"c"@ and not @"\'c\'"@. instance Pretty Char where pp = pp . (:"") -- | Behaves like 'Show': @1@ is printed to @"1"@. instance Pretty Int -- | Behaves like 'Show': @1.2@ is printed to @"1.2"@. instance Pretty Float -- | Behaves like 'Show': @1.2@ is printed to @"1.2"@. instance Pretty Double -- -- | Print a map @M.fromList [("k1","v1"), ("k2","v2")]@ -- -- as @"[ k1 → v1, k2 → v2 ]"@. -- instance (Pretty k, Pretty v) => Pretty (M.Map k v) where -- pp = foldl pp' (pp "") . M.toList where -- pp' s (k, v) = s +> k ~> "=>" ~> indented v +> nl -- | The 'Pretty1' type class lifts 'Pretty' printing to unary type constructors. -- It can be used in special cases to abstract over type constructors which -- are 'Pretty' printable for any 'Pretty' printable type argument. class Pretty1 f where pp1 :: Pretty a => f a → PrettyM () default pp1 :: Pretty (f a) => f a -> PrettyM () pp1 = pp -- | The 'Pretty2' type class lifts 'Pretty' printing to binary type constructors. -- It can be used in special cases to abstract over type constructors which -- are 'Pretty' printable for any 'Pretty' printable type arguments. class Pretty2 (f :: * → * → *) where pp2 :: (Pretty a, Pretty b) => f a b → PrettyM () default pp2 :: Pretty (f a b) => f a b -> PrettyM () pp2 = pp -- Pretty Monad ---------------------------------------------------------------- -- | The 'PrettyM' monad is run in the pretty printing process, e.g. in -- 'pretty' or 'prettyPrint'. -- -- 'PrettyM' is internally a 'State' monad manipulating a 'Config' and a list of -- pretty printed lines. -- -- Most of the combinators from this library take values of 'Pretty' printable types, -- convert them to @'PrettyM' ()@ actions using 'pp', and combine the actions in -- some way resulting in a new @'PrettyM' ()@ action. newtype PrettyM a = PrettyM { runPrettyM :: State PrettySt a } deriving (Functor, Applicative, Monad, MonadState PrettySt) -- | This instance makes it possible to nest operators like @('+>')@. -- Implemented as: @pp = id@ instance Pretty (PrettyM ()) where pp = id sepByList :: [[a]] → [a] → [[a]] sepByList [] _ = [] sepByList [s] _ = [s] sepByList (s:ss) s' = s : s' : sepByList ss s' addIndent :: PrettyM () addIndent = do i <- use indentation c <- use indentChar text . head1L %= (++ replicate i c) -- Indentation ----------------------------------------------------------------- indentByChars :: Int -> PrettyM () indentByChars = (indentation +=) indentByLevels :: Int -> PrettyM () indentByLevels i = (indentation +=) . (i *) =<< use indentDepth -- | Print an @a@ with indentation increased by a certain amount of -- 'cIndentChar' characters. -- -- Example: -- -- > pretty defConfig $ -- > "while (true) {" \> -- > indentedByChars 2 ("f();" \> "g();") \> -- > "}" -- > -- ↪ "while (true) { -- > -- f(); -- > -- g(); -- > -- }" indentedByChars :: Pretty a => Int -- ^ Number of characters to increase indentation. -> a -- ^ A 'Pretty' printable @a@ -> PrettyM () -- ^ An action printing the @a@ with increased indentation. indentedByChars i a = do indentByChars i pp a indentByChars (-i) -- | Same as 'indentedByChars' but increases indentation in 'cIndentDepth' steps. indentedBy :: Pretty a => Int -- ^ Number of indentation levels to increase. -- One indentation level consists of 'cIndentDepth' characters. -> a -- ^ A 'Pretty' printable @a@ -> PrettyM () -- ^ An action printing the @a@ with increased indentation. indentedBy i a = do indentByLevels i pp a indentByLevels (-i) -- | Convenience function defined as: -- -- > indented = indentedBy 1 indented :: Pretty a => a -- ^ A 'Pretty' printable @a@ -> PrettyM () -- ^ An action printing the @a@ indented 1 level deeper. indented = indentedBy 1 indentToCurPos :: PrettyM () indentToCurPos = do curLine ← use $ text . head1L indentation .= length curLine indentedToCurPos :: PrettyM a → PrettyM a indentedToCurPos ma = do i ← use indentation indentToCurPos a ← ma indentation .= i pure a -- Associativity & Fixity ------------------------------------------------------ withPrecedence :: (Assoc, Int) → PrettyM () → PrettyM () withPrecedence (a, p) ma = do p' ← use precedence a' ← use assoc precedence .= p assoc .= a if | p' == p && a' == a && a /= AssocN → ma | p' < p → ma | otherwise → do pp "("; ma; pp ")" precedence .= p' assoc .= a' -- | Print an @a@ as a left-associative operator of a certain fixity. assocL :: Pretty a => Int → a → PrettyM () assocL i = withPrecedence (AssocL, i) . pp -- | Print an @a@ as a right-associative operator of a certain fixity. assocR :: Pretty a => Int → a → PrettyM () assocR i = withPrecedence (AssocR, i) . pp -- | Print an @a@ as a non-associative operator of a certain fixity. assocN :: Pretty a => Int → a → PrettyM () assocN i = withPrecedence (AssocN, i) . pp -- | The constructors of this type can be used as short forms of 'left', -- 'right', and 'inner'. data AssocAnn a = L a -- ^ Print an @a@ as the left argument of a mixfix operator (behaves like 'left'). | R a -- ^ Print an @a@ as the right argument of a mixfix operator (behaves like 'right'). | I a -- ^ Print an @a@ as the inner argument of a mixfix operator (behaves like 'inner'). deriving (Eq, Ord, Read, Show) instance Pretty1 AssocAnn -- | Let the associativity annotations for arguments ('L', 'R', 'I') -- behave as the 'left', 'right', and 'inner' functions. instance Pretty a => Pretty (AssocAnn a) where pp = \case L a → left a R a → right a I a → inner a -- | Print an @a@ as the left argument of a mixfix operator. left :: Pretty a => a → PrettyM () left = assocDir AssocL -- | Print an @a@ as the right argument of a mixfix operator. right :: Pretty a => a → PrettyM () right = assocDir AssocR -- | Print an @a@ as an inner argument of a mixfix operator. inner :: Pretty a => a → PrettyM () inner ma = do p' ← use precedence a' ← use assoc precedence .= (-1) assoc .= AssocN pp ma precedence .= p' assoc .= a' assocDir :: Pretty a => Assoc → a → PrettyM () assocDir a ma = do a' ← use assoc if | a' == a → pp ma | otherwise → do assoc .= AssocN pp ma assoc .= a'