GenZ-0.1.0.0: lib/FormP/ParseTPTP.hs
module FormP.ParseTPTP where
import Data.Char
import Data.List
import FormP
import FormP.Parse
-- NOTE: This tptp parser was assisted by ChatGPT
-- NOTE by MG: better would be to properly write a TPTP grammar into Parse.y.
rewriteTPTPProblem :: String -> Either (Int,Int) String
rewriteTPTPProblem s = normalizeFormula <$> do
let blocks = fofBlocks s -- each fof(...) block
triples = map parseFOFHeader blocks -- [(name,role,formulaText)]
axTexts = [ f | (_,label,f) <- triples, label == "axiom" ]
conTexts = [ f | (_,label,f) <- triples, label == "conjecture" ]
cText <- case conTexts of
[] -> Left (0,0) -- no conjecture found
(x:_) -> Right x
case axTexts of
[] -> Right cText
(a:rest) -> Right (foldl (\ fs gs -> fs ++ " & " ++ gs) a rest ++ " -> " ++ cText)
-- NOTE by MG: The function `parseTPTPProblem` does too much, for the executable we
-- only want a `String -> String` before using the existing FormP parser.
-- | Parse .tptp file into a single formula: (A1 & ... & An) -> C
-- where each Ai is a formula with role = axiom, and C is the first conjecture.
parseTPTPProblem :: String -> ParseResult FormP
parseTPTPProblem s = do
let blocks = fofBlocks s -- each fof(...) block
triples = map parseFOFHeader blocks -- [(name,role,formulaText)]
axTexts = [ f | (_,label,f) <- triples, label == "axiom" ]
conTexts = [ f | (_,label,f) <- triples, label == "conjecture" ]
cText <- case conTexts of
[] -> Left (0,0) -- no conjecture found
(x:_) -> Right x
-- Parse each formula using the existing FormP parser
as <- mapM (scanParseSafe parseFormP . normalizeFormula) axTexts
c <- scanParseSafe parseFormP (normalizeFormula cText)
case as of
[] -> Right c
(a:rest) -> Right (ImpP (foldl ConP a rest) c)
-- | Split the whole file into lines and extract complete fof(...) blocks
-- (each block may span multiple lines).
fofBlocks :: String -> [String]
fofBlocks = go [] [] . lines
where
go acc cur [] =
reverse (if null cur then acc else unlines (reverse cur) : acc)
go acc cur (l:ls)
-- Skip comment lines
| "%" `isPrefixOf` dropWhile isSpace l =
go acc cur ls
-- Start of a new fof block: line begins with "fof("
| "fof(" `isPrefixOf` dropWhile isSpace l =
let acc' = if null cur
then acc
else unlines (reverse cur) : acc
in go acc' [l] ls
-- No fof started yet; keep looking
| null cur =
go acc cur ls
-- End of current fof block: line contains "))."
| "))." `isInfixOf` l =
go (unlines (reverse (l:cur)) : acc) [] ls
-- Middle line of the current fof block
| otherwise =
go acc (l:cur) ls
-- | Extract (name, role, formulaText) from a fof(...) block.
-- Input is roughly of the form:
-- fof(axiom1,axiom,( ( p1 <=> p2 ) => ( p1 & p2 ) )).
parseFOFHeader :: String -> (String, String, String)
parseFOFHeader block =
let flat = unwords (words block) -- flatten to a single line, normalize whitespace
flat' = dropWhile isSpace flat
-- Drop the leading "fof("
rest1 = dropPrefix "fof(" flat'
(name, rest2) = break (== ',') rest1
rest3 = drop 1 rest2 -- drop first comma
(rolePart, rest4) = break (== ',') rest3
role = trim rolePart
rest5 = drop 1 rest4 -- after second comma we expect "( ... )."
-- Strip the trailing ")." (fof terminator), keeping the formula's final ')'
(fmlPart, _) = breakOn ")." rest5
formulaText = trim fmlPart
in (trim name, role, formulaText)
-- | Map TPTP boolean constants to the identifiers understood by our lexer (in case they appear).
normalizeFormula :: String -> String
normalizeFormula =
replace "$false" "false"
. replace "$true" "true"
-- | Small helper functions
trim :: String -> String
trim = dropWhile isSpace . dropWhileEnd isSpace
dropPrefix :: String -> String -> String
dropPrefix pre s
| pre `isPrefixOf` s = drop (length pre) s
| otherwise = s
-- Split the string at the first occurrence of the given substring.
-- Returns (prefix, suffix-without-the-substring).
breakOn :: String -> String -> (String, String)
breakOn pat = go []
where
go acc "" = (reverse acc, "")
go acc s@(c:cs)
| pat `isPrefixOf` s = (reverse acc, drop (length pat) s)
| otherwise = go (c:acc) cs
replace :: String -> String -> String -> String
replace old new = go
where
go [] = []
go s@(c:cs)
| old `isPrefixOf` s = new ++ go (drop (length old) s)
| otherwise = c : go cs
-- DEBUGGING
-- Debug helper: print the (name, role, formulaText) triples extracted from a tptp file.
debugTPTP :: String -> IO ()
debugTPTP s = do
let blocks = fofBlocks s
triples = map parseFOFHeader blocks
putStrLn "Blocks/parsing result:"
mapM_ print triples