disco-0.1.0.0: src/Disco/Pretty.hs
{-# LANGUAGE DerivingVia #-}
{-# LANGUAGE NoMonomorphismRestriction #-}
{-# LANGUAGE OverloadedStrings #-}
-----------------------------------------------------------------------------
-- |
-- Module : Disco.Pretty
-- Copyright : disco team and contributors
-- Maintainer : byorgey@gmail.com
--
-- SPDX-License-Identifier: BSD-3-Clause
--
-- Various pretty-printing facilities for disco.
--
-----------------------------------------------------------------------------
-- TODO: the calls to 'error' should be replaced with logging/error capabilities.
module Disco.Pretty
( module Disco.Pretty.DSL
, module Disco.Pretty
, module Disco.Pretty.Prec
, Doc
)
where
import Prelude hiding ((<>))
import Data.Bifunctor
import Data.Char (isAlpha)
import Data.Map (Map)
import qualified Data.Map as M
import Data.Ratio
import Data.Set (Set)
import qualified Data.Set as S
import Disco.Effects.LFresh
import Polysemy
import Polysemy.Reader
import Text.PrettyPrint (Doc)
import Unbound.Generics.LocallyNameless (Name)
import Disco.Pretty.DSL
import Disco.Pretty.Prec
import Disco.Syntax.Operators
------------------------------------------------------------
-- Utilities for handling precedence and associativity
-- | Convenience function combining 'setPA' and 'mparens', since we
-- often want to simultaneously indicate what the precedence and
-- associativity of a term is, and optionally surround it with
-- parentheses depending on the precedence and associativity of its
-- parent.
withPA :: Member (Reader PA) r => PA -> Sem r Doc -> Sem r Doc
withPA pa = mparens pa . setPA pa
-- | Locally set the precedence and associativity within a
-- subcomputation.
setPA :: Member (Reader PA) r => PA -> Sem r a -> Sem r a
setPA = local . const
-- | Mark a subcomputation as pretty-printing a term on the left of an
-- operator (so parentheses can be inserted appropriately, depending
-- on the associativity).
lt :: Member (Reader PA) r => Sem r Doc -> Sem r Doc
lt = local (\(PA p _) -> PA p InL)
-- | Mark a subcomputation as pretty-printing a term on the right of
-- an operator (so parentheses can be inserted appropriately,
-- depending on the associativity).
rt :: Member (Reader PA) r => Sem r Doc -> Sem r Doc
rt = local (\(PA p _) -> PA p InR)
-- | Optionally surround a pretty-printed term with parentheses,
-- depending on its precedence and associativity (given as the 'PA'
-- argument) and that of its context (given by the ambient 'Reader
-- PA' effect).
mparens :: Member (Reader PA) r => PA -> Sem r Doc -> Sem r Doc
mparens pa doc = do
parentPA <- ask
(if pa < parentPA then parens else id) doc
------------------------------------------------------------
-- Pretty type class
class Pretty t where
pretty :: Members '[Reader PA, LFresh] r => t -> Sem r Doc
prettyStr :: Pretty t => t -> Sem r String
prettyStr = renderDoc . runLFresh . pretty
pretty' :: Pretty t => t -> Sem r Doc
pretty' = runReader initPA . runLFresh . pretty
------------------------------------------------------------
-- Some standard instances
instance Pretty a => Pretty [a] where
pretty = brackets . intercalate "," . map pretty
instance (Pretty k, Pretty v) => Pretty (Map k v) where
pretty m = do
let es = map (\(k,v) -> pretty k <+> "->" <+> pretty v) (M.assocs m)
ds <- setPA initPA $ punctuate "," es
braces (hsep ds)
instance Pretty a => Pretty (Set a) where
pretty = braces . intercalate "," . map pretty . S.toList
------------------------------------------------------------
-- Some Disco instances
instance Pretty (Name a) where
pretty = text . show
instance Pretty TyOp where
pretty = \case
Enumerate -> text "enumerate"
Count -> text "count"
-- | Pretty-print a unary operator, by looking up its concrete syntax
-- in the 'uopMap'.
instance Pretty UOp where
pretty op = case M.lookup op uopMap of
Just (OpInfo _ (syn:_) _) ->
text $ syn ++ (if all isAlpha syn then " " else "")
_ -> error $ "UOp " ++ show op ++ " not in uopMap!"
-- | Pretty-print a binary operator, by looking up its concrete syntax
-- in the 'bopMap'.
instance Pretty BOp where
pretty op = case M.lookup op bopMap of
Just (OpInfo _ (syn:_) _) -> text syn
_ -> error $ "BOp " ++ show op ++ " not in bopMap!"
--------------------------------------------------
-- Pretty-printing decimals
-- | Pretty-print a rational number using its decimal expansion, in
-- the format @nnn.prefix[rep]...@, with any repeating digits enclosed
-- in square brackets.
prettyDecimal :: Rational -> String
prettyDecimal r = printedDecimal
where
(n,d) = properFraction r :: (Integer, Rational)
(expan, len) = digitalExpansion 10 (numerator d) (denominator d)
printedDecimal
| length first102 > 101 || length first102 == 101 && last first102 /= 0
= show n ++ "." ++ concatMap show (take 100 expan) ++ "..."
| rep == [0]
= show n ++ "." ++ (if null pre then "0" else concatMap show pre)
| otherwise
= show n ++ "." ++ concatMap show pre ++ "[" ++ concatMap show rep ++ "]"
where
(pre, rep) = splitAt len expan
first102 = take 102 expan
-- Given a list, find the indices of the list giving the first and
-- second occurrence of the first element to repeat, or Nothing if
-- there are no repeats.
findRep :: Ord a => [a] -> ([a], Int)
findRep = findRep' M.empty 0
findRep' :: Ord a => M.Map a Int -> Int -> [a] -> ([a], Int)
findRep' _ _ [] = error "Impossible. Empty list in findRep'"
findRep' prevs ix (x:xs)
| x `M.member` prevs = ([], prevs M.! x)
| otherwise = first (x:) $ findRep' (M.insert x ix prevs) (ix+1) xs
-- | @digitalExpansion b n d@ takes the numerator and denominator of a
-- fraction n/d between 0 and 1, and returns a pair of (1) a list of
-- digits @ds@, and (2) a nonnegative integer k such that @splitAt k
-- ds = (prefix, rep)@, where the infinite base-b expansion of
-- n/d is 0.@(prefix ++ cycle rep)@. For example,
--
-- > digitalExpansion 10 1 4 = ([2,5,0], 2)
-- > digitalExpansion 10 1 7 = ([1,4,2,8,5,7], 0)
-- > digitalExpansion 10 3 28 = ([1,0,7,1,4,2,8,5], 2)
-- > digitalExpansion 2 1 5 = ([0,0,1,1], 0)
--
-- It works by performing the standard long division algorithm, and
-- looking for the first time that the remainder repeats.
digitalExpansion :: Integer -> Integer -> Integer -> ([Integer], Int)
digitalExpansion b n d = digits
where
longDivStep (_, r) = (b*r) `divMod` d
res = tail $ iterate longDivStep (0,n)
digits = first (map fst) (findRep res)