summaryrefslogtreecommitdiff
path: root/source/Tptp/UnsortedFirstOrder.hs
blob: 4fcf052bf502ef8297c16649918429bb13eaa539 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
{-# LANGUAGE NoImplicitPrelude #-}

-- | A lenient representation of first-order TPTP syntax that does not guarantee that
-- the expression is actually first-order.
module Tptp.UnsortedFirstOrder where

import Data.Char
import Data.List.NonEmpty (NonEmpty)
import Data.List.NonEmpty qualified as NonEmpty
import Data.String (IsString)
import Data.Text (Text)
import Data.Text qualified as Text
import Prelude hiding (head, tail)
import Prettyprinter
import Prettyprinter.Render.Text
import Text.Builder



isAsciiLetter :: Char -> Bool
isAsciiLetter c = isAsciiLower c || isAsciiUpper c

isAsciiAlphaNumOrUnderscore :: Char -> Bool
isAsciiAlphaNumOrUnderscore c = isAsciiLower c || isAsciiUpper c || isDigit c || c == '_'


-- | A TPTP atomic word, starting with a lowercase letter or enclosed in single quotes.
newtype AtomicWord = AtomicWord Text deriving (Show, Eq, Ord, IsString)

isProperAtomicWord :: Text -> Bool
isProperAtomicWord w = case Text.uncons w of
    Nothing -> False
    Just (head, tail) -> isAsciiLower head && Text.all isAsciiAlphaNumOrUnderscore tail

-- | A TPTP variable, written as a word starting with an uppercase letter.
newtype Variable = Variable Text deriving (Show, Eq, Ord, IsString)

isVariable :: Text -> Bool
isVariable var = case Text.uncons var of
    Nothing -> False -- Variables must be nonempty.
    Just (head, tail) -> isAsciiUpper head && Text.all isAsciiAlphaNumOrUnderscore tail


data Expr
    = Apply AtomicWord [Expr]
    | Var Variable
    | Top
    | Bottom
    | Eq Expr Expr
    | NotEq Expr Expr
    | Conn Connective Expr Expr
    | Not Expr
    | Quantified Quantifier (NonEmpty Variable) Expr
    deriving (Show, Eq, Ord)

pattern Const :: AtomicWord -> Expr
pattern Const x = Apply x []

data Quantifier = Forall | Exists deriving (Show, Eq, Ord)

data Connective = And | Or | Imply | Iff deriving (Show, Eq, Ord)

data Role
    = Axiom
    | AxiomUseful -- ^ Annotated axiom for trainig premise selection.
    | AxiomRedundant -- ^ Annotated axiom for trainig premise selection.
    | Hypothesis
    | Conjecture
    | NegatedConjecture
    deriving (Show, Eq, Ord)

data Name = NameAtomicWord AtomicWord | NameInt Int
    deriving (Show, Eq, Ord)


data AnnotatedFormula
    = AnnotatedFormula Name Role Expr
    deriving (Show, Eq, Ord)


newtype Task
    = Task [AnnotatedFormula]
    deriving (Show, Eq, Ord, Semigroup, Monoid)

toTextNewline :: Task -> Text
toTextNewline task = run (buildTask task <> char '\n')

toText :: Task -> Text
toText task = run (buildTask task)

singleQuoted :: Text -> Text
singleQuoted str = Text.snoc (Text.cons '\'' (escape str)) '\''
    where
        -- First escape backslashes, then single quotes.
        escape :: Text -> Text
        escape = Text.replace "'" "\\'" . Text.replace "\\" "\\\\"

buildTuple :: [Builder] -> Builder
buildTuple bs = char '(' <> intercalate (char ',') bs <> char ')'

buildList :: [Builder] -> Builder
buildList bs = char '[' <> intercalate (char ',') bs <> char ']'

renderTask :: Task -> Text
renderTask task = renderStrict (layoutPretty defaultLayoutOptions (prettyTask task))

buildAtomicWord :: AtomicWord -> Builder
buildAtomicWord (AtomicWord w) = text if isProperAtomicWord w then w else singleQuoted w

buildVariable :: Variable -> Builder
buildVariable (Variable v) = text (Text.replace "'" "_" v)


buildApply :: AtomicWord -> [Expr] -> Builder
buildApply f args = buildAtomicWord f <> buildTuple (map buildExpr args)

buildExpr :: Expr -> Builder
buildExpr = \case
    Apply f [] -> buildAtomicWord f
    Apply f args -> buildApply f args
    Var var -> buildVariable var
    Top -> text "$true"
    Bottom -> text "$false"
    Eq t1 t2 -> buildExpr t1 <> char '=' <> buildExpr t2
    NotEq t1 t2 -> buildExpr t1 <> text "!=" <> buildExpr t2
    Not f ->
        char '~' <> buildUnitary f
    Conn And f1 f2 ->
        buildAnd f1 <> char '&' <> buildAnd f2
    Conn Or f1 f2 ->
        buildOr f1 <> char '|' <> buildOr f2
    Conn Imply f1 f2 ->
        buildUnitary f1 <> "=>" <> buildUnitary f2
    Conn Iff f1 f2 ->
        buildUnitary f1 <> "<=>" <> buildUnitary f2
    Quantified quant vars f ->
        buildQuantifier quant <> buildList (map buildVariable (NonEmpty.toList vars)) <> char ':' <> buildUnitary f

isAtom :: Expr -> Bool
isAtom = \case
    Var{} -> True
    Apply{} -> True
    Top -> True
    Bottom -> True
    Eq{} -> True
    NotEq{} -> True
    _ -> False

buildQuantifier :: Quantifier -> Builder
buildQuantifier = \case
        Forall -> text "!"
        Exists -> text "?"

buildUnitary :: Expr -> Builder
buildUnitary = \case
    atom | isAtom atom -> buildExpr atom
    Quantified quant vars f ->
        buildQuantifier quant <> buildList (map buildVariable (NonEmpty.toList vars)) <> char ':'  <> buildUnitary f
    Not phi -> char '~' <> buildUnitary phi
    phi -> char '(' <> buildExpr phi <> char ')'

buildAnd :: Expr -> Builder
buildAnd = \case
    Conn And f1 f2 -> buildAnd f1 <> char '&' <> buildAnd f2
    f -> buildUnitary f

buildOr :: Expr -> Builder
buildOr = \case
    Conn Or f1 f2 -> buildOr f1 <> char '|' <> buildUnitary f2
    f -> buildUnitary f

buildName :: Name -> Builder
buildName = \case
        NameAtomicWord w -> buildAtomicWord w
        NameInt n -> decimal n

buildRole :: Role -> Builder
buildRole = \case
        Axiom -> "axiom"
        AxiomUseful -> "axiom_useful"
        AxiomRedundant -> "axiom_redundant"
        Hypothesis -> "hypothesis"
        Conjecture -> "conjecture"
        NegatedConjecture -> "negated_conjecture"

buildAnnotatedFormula :: AnnotatedFormula -> Builder
buildAnnotatedFormula (AnnotatedFormula name role phi) =
        "fof" <> buildTuple [buildName name, buildRole role, buildExpr phi] <> "."

buildTask :: Task -> Builder
buildTask (Task fofs) = intercalate (char '\n') (map buildAnnotatedFormula fofs)



prettyAtomicWord :: AtomicWord -> Doc ann
prettyAtomicWord (AtomicWord w) =
        if isProperAtomicWord w
            then pretty w
            else pretty (singleQuoted w)

prettyVariable :: Variable -> Doc ann
prettyVariable (Variable v) = pretty v


prettyApply :: AtomicWord -> [Expr] -> Doc ann
prettyApply f args = prettyAtomicWord f <> tupled (map prettyExpr args)

-- | @&@ and @|@ are associative, all other connectives are nonassociative.
-- The prettyprinting of these associative connectives does not preserve
-- the precise parenthesization but instead minimizes parentheses in the output.
prettyExpr :: Expr -> Doc ann
prettyExpr = \case
    Apply f [] -> prettyAtomicWord f
    Apply f args -> prettyApply f args
    Var var -> prettyVariable var
    Top -> "$true"
    Bottom -> "$false"
    Eq t1 t2 -> prettyExpr t1 <+> "=" <+> prettyExpr t2
    NotEq t1 t2 -> prettyExpr t1 <+> "!=" <+> prettyExpr t2
    atom | isAtom atom ->
        prettyExpr atom
    Not f ->
        "~" <+> prettyUnitary f
    Conn And f1 f2 ->
        prettyAnd f1 <+> "&" <+> prettyAnd f2
    Conn Or f1 f2 ->
        prettyOr f1 <+> "|" <+> prettyOr f2
    Conn Imply f1 f2 ->
        prettyUnitary f1 <+> "=>" <+> prettyUnitary f2
    Conn Iff f1 f2 ->
        prettyUnitary f1 <+> "<=>" <+> prettyUnitary f2
    Quantified quant vars f ->
        prettyQuantifier quant <+> list (map prettyVariable (NonEmpty.toList vars)) <> ":" <+> prettyUnitary f

prettyQuantifier :: Quantifier -> Doc ann
prettyQuantifier = \case
        Forall -> "!"
        Exists -> "?"

prettyUnitary :: Expr -> Doc ann
prettyUnitary = \case
    atom | isAtom atom -> prettyExpr atom
    Quantified quant vars f ->
        prettyQuantifier quant <+> list (map prettyVariable (NonEmpty.toList vars)) <> ":" <+> prettyUnitary f
    Not phi -> "~" <+> prettyUnitary phi
    phi -> parens (prettyExpr phi)

prettyAnd :: Expr -> Doc ann
prettyAnd = \case
    Conn And f1 f2 -> prettyAnd f1 <+> "&" <+> prettyAnd f2
    f -> prettyUnitary f

prettyOr :: Expr -> Doc ann
prettyOr = \case
    Conn Or f1 f2 -> prettyOr f1 <+> "|" <+> prettyUnitary f2
    f -> prettyUnitary f

prettyName :: Name -> Doc ann
prettyName = \case
        NameAtomicWord w -> prettyAtomicWord w
        NameInt n -> pretty n

prettyRole :: Role -> Doc ann
prettyRole = \case
        Axiom -> "axiom"
        AxiomUseful -> "axiom_useful"
        AxiomRedundant -> "axiom_redundant"
        Hypothesis -> "hypothesis"
        Conjecture -> "conjecture"
        NegatedConjecture -> "negated_conjecture"

prettyAnnotatedFormula :: AnnotatedFormula -> Doc ann
prettyAnnotatedFormula (AnnotatedFormula name role phi) =
        "fof" <> tupled [prettyName name, prettyRole role, prettyExpr phi] <> "."

prettyTask :: Task -> Doc ann
prettyTask (Task fofs) = vsep (map prettyAnnotatedFormula fofs)