tptp-0.1.2.0: test/QuickCheckSpec/ArbitrarilyPretty.hs
{-# LANGUAGE DeriveFunctor, DeriveTraversable, DeriveFoldable #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE CPP #-}
-- |
-- Module : ArbitrarilyPretty
-- Description : Generators for randomized pretty outputs of TPTP expressions.
-- Copyright : (c) Evgenii Kotelnikov, 2020
-- License : GPL-3
-- Maintainer : evgeny.kotelnikov@gmail.com
-- Stability : experimental
--
-- Defines a class 'ArbitrarilyPretty' used for randomized pretty printing along
-- with instances of this class implementing substandard ways to pretty print
-- a TPTP expression.
--
module ArbitrarilyPretty (
ArbitrarilyPretty(..),
SuperfluousParenthesis(..),
randomSplit,
randomTree
) where
import Data.List.NonEmpty (NonEmpty(..))
import qualified Data.List.NonEmpty as NEL (splitAt, fromList, length)
#if MIN_VERSION_base(4, 8, 0)
import Prelude hiding ((<$>), (<*>))
#else
import Prelude hiding (mapM)
import Data.Foldable (Foldable)
import Data.Traversable (Traversable, mapM)
import Data.Monoid (mempty)
#endif
import qualified Data.Foldable as F (toList)
import Data.List (genericReplicate, intersperse)
import Data.Maybe (maybeToList)
import Data.Text (Text)
#if !MIN_VERSION_base(4, 11, 0)
import Data.Semigroup ((<>))
#endif
import Control.Applicative ((<$>), (<*>))
import Test.QuickCheck (Gen, choose, frequency)
import Data.Text.Prettyprint.Doc (
Doc, (<+>), parens, brackets, punctuate, surround, hsep, space, comma
)
import Data.TPTP
import Data.TPTP.Pretty
-- | The class whose instances provide various ways of randomly pretty print
-- an expression.
class ArbitrarilyPretty e where
apretty :: forall a. e -> Gen (Doc a)
-- * Helpers
(&) :: (a -> b) -> (b -> c) -> a -> c
(&) = flip (.)
-- | Randomply split a non-empty list into two lists.
randomSplit :: NonEmpty a -> Gen ([a], NonEmpty a)
randomSplit lst = do
index <- choose (0, NEL.length lst - 1)
let (prefix, suffix) = NEL.splitAt index lst
return (prefix, NEL.fromList suffix)
-- | Randomly split a non-empty list into a binary tree such that collecting
-- the leafs of that tree from left to right results in the original list.
--
-- This splitting is used to generate randomly nested disjunctions with
-- superfluous parenthesis compared to an equivalent clause.
randomTree :: NonEmpty a -> Gen (BinTree a)
randomTree = \case
a :| [] -> return (Leaf a)
a :| a' : as -> do
(prefix, suffix) <- randomSplit (a' :| as)
Fork <$> randomTree (a :| prefix) <*> randomTree suffix
star, arrow, tType, colon, negation :: Text
star = "*"
arrow = ">"
tType = "$tType"
colon = ":"
negation = "~"
-- * Superfluous parenthesis
-- | An auxiliary wrapper used to generate pretty printed TPTP expressions
-- with superfluous parenthesis.
newtype SuperfluousParenthesis a = SuperfluousParenthesis {
unSuperfluousParenthesis :: a
} deriving (Eq, Show, Ord, Functor, Traversable, Foldable)
-- | Randomly pretty print a TPTP expression with superflouous parenthesis
-- somewhere inside the expression but not on the outer level.
sppretty :: ArbitrarilyPretty (SuperfluousParenthesis e)
=> e -> forall a. Gen (Doc a)
sppretty = apretty . SuperfluousParenthesis
-- | Map a given 'Doc' generator to a one that randomly wraps the document
-- in zero, one or two pairs of parenthesis.
superfluousParenthesis :: Gen (Doc a) -> Gen (Doc a)
superfluousParenthesis g = frequency [
(2, g),
(2, parens <$> g),
(1, parens . parens <$> g)
]
-- ** SuperfluousParenthesis instances for common types
instance (ArbitrarilyPretty (SuperfluousParenthesis a),
ArbitrarilyPretty (SuperfluousParenthesis b)) =>
ArbitrarilyPretty (SuperfluousParenthesis (Either a b)) where
apretty = either sppretty sppretty . unSuperfluousParenthesis
instance ArbitrarilyPretty (SuperfluousParenthesis Text) where
apretty = return . pretty . unSuperfluousParenthesis
-- ** Auxiliary data types and their SuperfluousParenthesis instances
-- | An auxiliary wrapper used to pretty print applications of function and
-- predicate symbols with superfluous parenthesis.
data Application s e = Application_ s [e]
deriving (Eq, Ord, Show)
-- | Pretty print an application of a symbol to a list of arguments.
application :: Pretty f => f -> [Doc a] -> Doc a
application f [] = pretty f
application f as = pretty f <> parens (hsep (punctuate comma as))
instance (ArbitrarilyPretty (SuperfluousParenthesis e), Pretty s) =>
ArbitrarilyPretty (SuperfluousParenthesis (Application s e)) where
apretty = unSuperfluousParenthesis & \case
Application_ s ts -> application s
<$> mapM (superfluousParenthesis . sppretty) ts
-- | An auxiliary wrapper used to pretty print applications of infix operators
-- with superfluous parenthesis.
data Infix e o = Infix o e e
deriving (Eq, Ord, Show)
instance (ArbitrarilyPretty (SuperfluousParenthesis e), Pretty o) =>
ArbitrarilyPretty (SuperfluousParenthesis (Infix e o)) where
apretty = unSuperfluousParenthesis & \case
Infix o a b -> surround (space <> pretty o <> space)
<$> superfluousParenthesis (sppretty a)
<*> superfluousParenthesis (sppretty b)
-- | An auxiliary wrapper used to pretty print applications of prefix operators
-- with superfluous parenthesis.
data Prefix e o = Prefix o e
deriving (Eq, Ord, Show)
instance (ArbitrarilyPretty (SuperfluousParenthesis e), Pretty o) =>
ArbitrarilyPretty (SuperfluousParenthesis (Prefix e o)) where
apretty = unSuperfluousParenthesis & \case
Prefix o e -> (pretty o <+>) <$> superfluousParenthesis (sppretty e)
-- | An auxiliary wrapper used to pretty print expressions with superfluous
-- parenthesis with a condition that mandates at least one pair of parenthesis.
data ParensUnless e = ParensUnless Bool e
deriving (Show, Eq, Ord)
instance ArbitrarilyPretty (SuperfluousParenthesis e) =>
ArbitrarilyPretty (SuperfluousParenthesis (ParensUnless e)) where
apretty = unSuperfluousParenthesis & \case
ParensUnless p e -> if p then sppretty e else parens <$> sppretty e
-- | An auxiliary data structure used to pretty print a binary tree of disjunctions.
data BinTree t
= Leaf t
| Fork (BinTree t) (BinTree t)
deriving (Eq, Ord, Show, Functor, Traversable, Foldable)
instance ArbitrarilyPretty (SuperfluousParenthesis l) =>
ArbitrarilyPretty (SuperfluousParenthesis (BinTree l)) where
apretty = unSuperfluousParenthesis & \case
Leaf l -> sppretty l
Fork a b -> sppretty (Infix Disjunction a b)
-- | An auxiliary wrapper used to pretty print a typing of a symbol or a variable
-- with superfluous parenthesis around the type.
data Typing a t = Typing_ a t
deriving (Eq, Show, Ord)
instance (ArbitrarilyPretty (SuperfluousParenthesis t), Pretty a) =>
ArbitrarilyPretty (SuperfluousParenthesis (Typing a t)) where
apretty = unSuperfluousParenthesis & \case
Typing_ a t -> (pretty a <>) <$> sppretty (Prefix colon t)
-- | An auxiliary wrapper used to pretty print a mapping type with superfluous
-- parenthesis around the argument type and the return type.
data Mapping s = Mapping [s] s
deriving (Eq, Show, Ord, Functor, Traversable, Foldable)
instance ArbitrarilyPretty (SuperfluousParenthesis s) =>
ArbitrarilyPretty (SuperfluousParenthesis (Mapping s)) where
apretty = unSuperfluousParenthesis & \case
Mapping [] s -> sppretty s
m -> do
Mapping as s <- mapM (superfluousParenthesis . sppretty) m
let starSeparated = hsep . intersperse (pretty star)
return (parens (starSeparated as) <+> pretty arrow <+> s)
-- ** First-order logic
instance ArbitrarilyPretty (SuperfluousParenthesis Term) where
apretty = unSuperfluousParenthesis & \case
Function f ts -> sppretty (Application_ f ts)
Variable v -> return (pretty v)
Number i -> return (pretty i)
DistinctTerm d -> return (pretty d)
instance ArbitrarilyPretty (SuperfluousParenthesis Literal) where
apretty = unSuperfluousParenthesis & \case
Predicate p ts -> sppretty (Application_ p ts)
Equality a s b -> sppretty (Infix s a b)
instance ArbitrarilyPretty (SuperfluousParenthesis (Sign, Literal)) where
apretty = unSuperfluousParenthesis & \case
(Positive, l) -> sppretty l
(Negative, l) -> sppretty (Prefix negation l)
instance ArbitrarilyPretty (SuperfluousParenthesis Clause) where
apretty = unSuperfluousParenthesis & \case
Clause ls -> randomTree ls >>= sppretty
unitary :: FirstOrder s -> Bool
unitary = \case
Atomic{} -> True
Negated{} -> True
Quantified{} -> True
Connected{} -> False
under :: Connective -> FirstOrder s -> Bool
under c = \case
Connected _ c' _ -> c' == c && isAssociative c
f -> unitary f
instance ArbitrarilyPretty (SuperfluousParenthesis s) =>
ArbitrarilyPretty (SuperfluousParenthesis (FirstOrder s)) where
apretty = unSuperfluousParenthesis & \case
Atomic l -> sppretty l
Negated g -> sppretty (Prefix negation (ParensUnless (unitary g) g))
Connected g c h -> sppretty (Infix c (ParensUnless (under c g) g)
(ParensUnless (under c h) h))
Quantified q vs g -> do
let var (v, s) = (pretty v <>) <$> sppretty s
vs' <- mapM var (F.toList vs)
g' <- sppretty (Prefix colon (ParensUnless (unitary g) g))
return (pretty q <+> brackets (hsep (punctuate comma vs')) <> g')
-- ** Sorts and types
instance ArbitrarilyPretty (SuperfluousParenthesis Unsorted) where
apretty = return . pretty . unSuperfluousParenthesis
instance ArbitrarilyPretty (SuperfluousParenthesis s) =>
ArbitrarilyPretty (SuperfluousParenthesis (Sorted s)) where
apretty = unSuperfluousParenthesis & \case
Sorted Nothing -> return mempty
Sorted (Just s) -> sppretty (Prefix colon s)
instance ArbitrarilyPretty (SuperfluousParenthesis (Name Sort)) where
apretty = return . pretty . unSuperfluousParenthesis
instance ArbitrarilyPretty (SuperfluousParenthesis QuantifiedSort) where
apretty = return . pretty . unSuperfluousParenthesis
instance ArbitrarilyPretty (SuperfluousParenthesis TFF1Sort) where
apretty = unSuperfluousParenthesis & \case
SortVariable v -> return (pretty v)
TFF1Sort f ss -> sppretty (Application_ f ss)
instance ArbitrarilyPretty (SuperfluousParenthesis Type) where
apretty = unSuperfluousParenthesis & \case
Type as r -> sppretty (Mapping as r)
TFF1Type [] as r -> sppretty (Mapping as r)
TFF1Type vs as r -> do
vs' <- mapM sppretty (fmap (flip Typing_ tType) vs)
t' <- sppretty (Prefix colon (ParensUnless (null as) (TFF1Type [] as r)))
return ("!>" <+> brackets (hsep (punctuate comma vs')) <> t')
-- ** Units
instance ArbitrarilyPretty (SuperfluousParenthesis Formula) where
apretty = unSuperfluousParenthesis & \case
CNF c -> sppretty c
FOF g -> sppretty g
TFF0 g -> sppretty g
TFF1 g -> sppretty g
instance ArbitrarilyPretty (SuperfluousParenthesis Declaration) where
apretty = unSuperfluousParenthesis & \case
Formula _ f -> sppretty f
Typing s t -> sppretty (Typing_ s t)
Sort s n -> sppretty (Typing_ s (Mapping (genericReplicate n tType) tType))
instance ArbitrarilyPretty (SuperfluousParenthesis Unit) where
apretty = unSuperfluousParenthesis & \case
i@Include{} -> return (pretty i)
Unit nm decl a -> do
decl' <- superfluousParenthesis (sppretty decl)
let args = pretty nm : prettyRole decl : decl' : ann
return (application (declarationLanguage decl) args <> ".")
where
prettyRole = \case
Sort{} -> "type"
Typing{} -> "type"
Formula r _ -> pretty (name r)
ann = case a of
Just (s, i) -> pretty s : maybeToList (fmap prettyList i)
Nothing -> []