-- Parse and typecheck TPTP clauses, stopping at include-clauses. {-# LANGUAGE BangPatterns, MultiParamTypeClasses, ImplicitParams, FlexibleInstances, TypeOperators, TypeFamilies #-} module Jukebox.TPTP.ClauseParser where import Jukebox.TPTP.Parsec import Control.Applicative import Control.Monad import qualified Data.ByteString.Lazy.Char8 as BSL import qualified Data.ByteString.Char8 as BS import qualified Jukebox.Map as Map import Jukebox.Map(Map) import qualified Jukebox.Seq as S import Jukebox.Seq(Seq) import Data.List import Jukebox.TPTP.Print import Jukebox.Name hiding (name) import qualified Jukebox.NameMap as NameMap import Jukebox.TPTP.Lexer hiding (Pos, Error, Include, Var, Type, Not, ForAll, Exists, And, Or, Type, Apply, Implies, Follows, Xor, Nand, Nor, keyword, defined, kind) import qualified Jukebox.TPTP.Lexer as L import qualified Jukebox.Form as Form import Jukebox.Form hiding (tag, kind, Axiom, Conjecture, Question, newFunction, TypeOf(..)) import qualified Jukebox.Name as Name -- The parser monad data ParseState = MkState ![Input Form] -- problem being constructed, inputs are in reverse order !(Map BS.ByteString Type) -- types !(Map BS.ByteString (Name ::: FunType)) -- functions !(Map BS.ByteString (Name ::: Type)) -- free variables in CNF clause Type -- the $i type !(Closed ()) -- name generation type Parser = Parsec ParsecState type ParsecState = UserState ParseState TokenStream -- An include-clause. data IncludeStatement = Include BS.ByteString (Maybe [Tag]) deriving Show -- The initial parser state. initialState :: ParseState initialState = MkState [] (Map.insert (BS.pack "$i") typeI Map.empty) Map.empty Map.empty typeI closed0 where typeI = Type nameI Infinite Infinite instance Stream TokenStream Token where primToken (At _ (Cons Eof _)) ok err fatal = err primToken (At _ (Cons L.Error _)) ok err fatal = fatal "Lexical error" primToken (At _ (Cons t ts)) ok err fatal = ok ts t type Position TokenStream = TokenStream position = id -- Wee function for testing. testParser :: Parser a -> String -> Either [String] a testParser p s = snd (run (const []) p (UserState initialState (scan (BSL.pack s)))) getProblem :: Parser [Input Form] getProblem = do MkState p _ _ _ _ _ <- getState return (reverse p) -- Primitive parsers. {-# INLINE keyword' #-} keyword' p = satisfy p' where p' Atom { L.keyword = k } = p k p' _ = False {-# INLINE keyword #-} keyword k = keyword' (== k) "'" ++ show k ++ "'" {-# INLINE punct' #-} punct' p = satisfy p' where p' Punct { L.kind = k } = p k p' _ = False {-# INLINE punct #-} punct k = punct' (== k) "'" ++ show k ++ "'" {-# INLINE defined' #-} defined' p = fmap L.defined (satisfy p') where p' Defined { L.defined = d } = p d p' _ = False {-# INLINE defined #-} defined k = defined' (== k) "'" ++ show k ++ "'" {-# INLINE variable #-} variable = fmap name (satisfy p) "variable" where p L.Var{} = True p _ = False {-# INLINE number #-} number = fmap value (satisfy p) "number" where p Number{} = True p _ = False {-# INLINE atom #-} atom = fmap name (keyword' (const True)) "atom" -- Combinators. parens, bracks :: Parser a -> Parser a {-# INLINE parens #-} parens p = between (punct LParen) (punct RParen) p {-# INLINE bracks #-} bracks p = between (punct LBrack) (punct RBrack) p -- Build an expression parser from a binary-connective parser -- and a leaf parser. binExpr :: Parser a -> Parser (a -> a -> Parser a) -> Parser a binExpr leaf op = do lhs <- leaf do { f <- op; rhs <- binExpr leaf op; f lhs rhs } <|> return lhs -- Parsing clauses. -- Parse as many things as possible until EOF or an include statement. section :: (Tag -> Bool) -> Parser (Maybe IncludeStatement) section included = skipMany (input included) >> (fmap Just include <|> (eof >> return Nothing)) -- A single non-include clause. input :: (Tag -> Bool) -> Parser () input included = declaration Cnf (formulaIn cnf) <|> declaration Fof (formulaIn fof) <|> declaration Tff (\tag -> formulaIn tff tag <|> typeDeclaration) where {-# INLINE declaration #-} declaration k m = do keyword k parens $ do t <- tag punct Comma -- Don't bother typechecking clauses that we are not -- supposed to include in the problem (seems in the -- spirit of TPTP's include mechanism) if included t then m t else balancedParens punct Dot return () formulaIn lang tag = do k <- kind punct Comma form <- lang newFormula (k tag form) balancedParens = skipMany (parens balancedParens <|> (satisfy p >> return ())) p Punct{L.kind=LParen} = False p Punct{L.kind=RParen} = False p _ = True -- A TPTP kind. kind :: Parser (Tag -> Form -> Input Form) kind = axiom Axiom <|> axiom Hypothesis <|> axiom Definition <|> axiom Assumption <|> axiom Lemma <|> axiom Theorem <|> general Conjecture Form.Conjecture <|> general NegatedConjecture Form.Axiom <|> general Question Form.Question where axiom t = general t Form.Axiom general k kind = keyword k >> return (mk kind) mk kind tag form = Input { Form.tag = tag, Form.kind = kind, Form.what = form } -- A formula name. tag :: Parser Tag tag = atom <|> fmap (BS.pack . show) number "clause name" -- An include declaration. include :: Parser IncludeStatement include = do keyword L.Include res <- parens $ do name <- atom "quoted filename" clauses <- do { punct Comma ; fmap Just (bracks (sepBy1 tag (punct Comma))) } <|> return Nothing return (Include name clauses) punct Dot return res -- Inserting types, functions and clauses. newFormula :: Input Form -> Parser () newFormula input = do MkState p t f v i n <- getState putState (MkState (input:p) t f Map.empty i n) newNameFrom :: Named a => Closed () -> a -> (Closed (), Name) newNameFrom n name = (close_ n' (return ()), open n') where n' = close_ n (newName name) {-# INLINE findType #-} findType :: BS.ByteString -> Parser Type findType name = do MkState p t f v i n <- getState case Map.lookup name t of Nothing -> do let (n', name') = newNameFrom n name ty = Type { tname = name', tmonotone = Infinite, tsize = Infinite } putState (MkState p (Map.insert name ty t) f v i n') return ty Just x -> return x newFunction :: BS.ByteString -> FunType -> Parser (Name ::: FunType) newFunction name ty' = do f@(_ ::: ty) <- lookupFunction ty' name unless (ty == ty') $ do fatalError $ "Constant " ++ BS.unpack name ++ " was declared to have type " ++ prettyShow ty' ++ " but already has type " ++ prettyShow ty return f {-# INLINE applyFunction #-} applyFunction :: BS.ByteString -> [Term] -> Type -> Parser Term applyFunction name args' res = do i <- individual f@(_ ::: ty) <- lookupFunction (FunType (replicate (length args') i) res) name unless (map typ args' == args ty) $ typeError f args' return (f :@: args') {-# NOINLINE typeError #-} typeError f@(x ::: ty) args' = do let plural 1 x y = x plural _ x y = y fatalError $ "Type mismatch in term '" ++ prettyShow (f :@: args') ++ "': " ++ "Constant " ++ prettyShow x ++ if length (args ty) == length args' then " has type " ++ prettyShow ty ++ " but was applied to " ++ plural (length args') "an argument" "arguments" ++ " of type " ++ prettyShow (map typ args') else " has arity " ++ show (length args') ++ " but was applied to " ++ show (length (args ty)) ++ plural (length (args ty)) " argument" " arguments" {-# INLINE lookupFunction #-} lookupFunction :: FunType -> BS.ByteString -> Parser (Name ::: FunType) lookupFunction def name = do MkState p t f v i n <- getState case Map.lookup name f of Nothing -> do let (n', name') = newNameFrom n name decl = name' ::: def putState (MkState p t (Map.insert name decl f) v i n') return decl Just f -> return f -- The type $i (anything whose type is not specified gets this type) {-# INLINE individual #-} individual :: Parser Type individual = do MkState _ _ _ _ i _ <- getState return i -- Parsing formulae. cnf, tff, fof :: Parser Form cnf = let ?binder = fatalError "Can't use quantifiers in CNF" ?ctx = Nothing in fmap (ForAll . bind) formula tff = let ?binder = varDecl True ?ctx = Just Map.empty in formula fof = let ?binder = varDecl False ?ctx = Just Map.empty in formula -- We cannot always know whether what we are parsing is a formula or a -- term, since we don't have lookahead. For example, p(x) might be a -- formula, but in p(x)=y, p(x) is a term. -- -- To deal with this, we introduce the Thing datatype. -- A thing is either a term or a formula, or a literal that we don't know -- if it should be a term or a formula. Instead of a separate formula-parser -- and term-parser we have a combined thing-parser. data Thing = Apply !BS.ByteString ![Term] | Term !Term | Formula !Form instance Show Thing where show (Apply f []) = BS.unpack f show (Apply f args) = BS.unpack f ++ case args of [] -> "" args -> prettyShow args show (Term t) = prettyShow t show (Formula f) = prettyShow f -- However, often we do know whether we want a formula or a term, -- and there it's best to use a specialised parser (not least because -- the error messages are better). For that reason, our parser is -- parametrised on the type of thing you want to parse. We have two -- main parsers: -- * 'term' parses an atomic expression -- * 'formula' parses an arbitrary expression -- You can instantiate 'term' for Term, Form or Thing; in each case -- you get an appropriate parser. You can instantiate 'formula' for -- Form or Thing. -- Types for which a term f(...) is a valid literal. These are the types on -- which you can use 'term'. class TermLike a where -- Convert from a Thing. fromThing :: Thing -> Parser a -- Parse a variable occurrence as a term on its own, if that's allowed. var :: (?ctx :: Maybe (Map BS.ByteString Variable)) => Parser a -- A parser for this type. parser :: (?binder :: Parser Variable, ?ctx :: Maybe (Map BS.ByteString Variable)) => Parser a instance TermLike Form where {-# INLINE fromThing #-} fromThing t@(Apply x xs) = fmap (Literal . Pos . Tru) (applyFunction x xs O) fromThing (Term _) = mzero fromThing (Formula f) = return f -- A variable itself is not a valid formula. var = mzero parser = formula instance TermLike Term where {-# INLINE fromThing #-} fromThing t@(Apply x xs) = individual >>= applyFunction x xs fromThing (Term t) = return t fromThing (Formula _) = mzero parser = term var = do x <- variable case ?ctx of Nothing -> do MkState p t f vs i n <- getState case Map.lookup x vs of Just v -> return (Var v) Nothing -> do let (n', name) = newNameFrom n x v = name ::: i putState (MkState p t f (Map.insert x v vs) i n') return (Var v) Just ctx -> case Map.lookup x ctx of Just v -> return (Var v) Nothing -> fatalError $ "unbound variable " ++ BS.unpack x instance TermLike Thing where fromThing = return var = fmap Term var parser = formula -- Types that can represent formulae. These are the types on which -- you can use 'formula'. class TermLike a => FormulaLike a where fromFormula :: Form -> a instance FormulaLike Form where fromFormula = id instance FormulaLike Thing where fromFormula = Formula -- An atomic expression. {-# SPECIALISE term :: (?binder :: Parser Variable, ?ctx :: Maybe (Map BS.ByteString Variable)) => Parser Term #-} {-# SPECIALISE term :: (?binder :: Parser Variable, ?ctx :: Maybe (Map BS.ByteString Variable)) => Parser Form #-} {-# SPECIALISE term :: (?binder :: Parser Variable, ?ctx :: Maybe (Map BS.ByteString Variable)) => Parser Thing #-} term :: (?binder :: Parser Variable, ?ctx :: Maybe (Map BS.ByteString Variable), TermLike a) => Parser a term = function <|> var <|> parens parser where {-# INLINE function #-} function = do x <- atom args <- parens (sepBy1 term (punct Comma)) <|> return [] fromThing (Apply x args) literal, unitary, quantified, formula :: (?binder :: Parser Variable, ?ctx :: Maybe (Map BS.ByteString Variable), FormulaLike a) => Parser a {-# INLINE literal #-} literal = true <|> false <|> binary "literal" where {-# INLINE true #-} true = do { defined DTrue; return (fromFormula (And S.Nil)) } {-# INLINE false #-} false = do { defined DFalse; return (fromFormula (Or S.Nil)) } binary = do x <- term :: Parser Thing let {-# INLINE f #-} f p sign = do punct p lhs <- fromThing x :: Parser Term rhs <- term :: Parser Term let form = Literal . sign $ lhs :=: rhs when (typ lhs /= typ rhs) $ fatalError $ "Type mismatch in equality '" ++ prettyShow form ++ "': left hand side has type " ++ prettyShow (typ lhs) ++ " but right hand side has type " ++ prettyShow (typ rhs) return (fromFormula form) f Eq Pos <|> f Neq Neg <|> fromThing x {-# SPECIALISE unitary :: (?binder :: Parser Variable, ?ctx :: Maybe (Map BS.ByteString Variable)) => Parser Form #-} {-# SPECIALISE unitary :: (?binder :: Parser Variable, ?ctx :: Maybe (Map BS.ByteString Variable)) => Parser Thing #-} unitary = negation <|> quantified <|> literal where {-# INLINE negation #-} negation = do punct L.Not fmap (fromFormula . Not) (unitary :: Parser Form) {-# INLINE quantified #-} quantified = do q <- (punct L.ForAll >> return ForAll) <|> (punct L.Exists >> return Exists) vars <- bracks (sepBy1 ?binder (punct Comma)) let Just ctx = ?ctx ctx' = foldl' (\m v -> Map.insert (Name.base (Name.name v)) v m) ctx vars punct Colon rest <- let ?ctx = Just ctx' in (unitary :: Parser Form) return (fromFormula (q (Bind (NameMap.fromList vars) rest))) -- A general formula. {-# SPECIALISE formula :: (?binder :: Parser Variable, ?ctx :: Maybe (Map BS.ByteString Variable)) => Parser Form #-} {-# SPECIALISE formula :: (?binder :: Parser Variable, ?ctx :: Maybe (Map BS.ByteString Variable)) => Parser Thing #-} formula = do x <- unitary :: Parser Thing let binop op t u = op (S.Unit t `S.append` S.Unit u) {-# INLINE connective #-} connective p op = do punct p lhs <- fromThing x rhs <- formula :: Parser Form return (fromFormula (op lhs rhs)) connective L.And (binop And) <|> connective L.Or (binop Or) <|> connective Iff Equiv <|> connective L.Implies (Connective Implies) <|> connective L.Follows (Connective Follows) <|> connective L.Xor (Connective Xor) <|> connective L.Nor (Connective Nor) <|> connective L.Nand (Connective Nand) <|> fromThing x -- varDecl True: parse a typed variable binding X:a or an untyped one X -- varDecl False: parse an untyped variable binding X varDecl :: Bool -> Parser Variable varDecl typed = do x <- variable ty <- do { punct Colon; when (not typed) $ fatalError "Used a typed quantification in an untyped formula"; type_ } <|> individual MkState p t f v i n <- getState let (n', name) = newNameFrom n x putState (MkState p t f v i n') return (name ::: ty) -- Parse a type type_ :: Parser Type type_ = do { name <- atom; findType name } <|> do { defined DI; individual } -- A little data type to help with parsing types. data Type_ = TType | Fun [Type] Type | Prod [Type] prod :: Type_ -> Type_ -> Parser Type_ prod (Prod tys) (Prod tys2) | not (O `elem` tys ++ tys2) = return $ Prod (tys ++ tys2) prod _ _ = fatalError "invalid type" arrow :: Type_ -> Type_ -> Parser Type_ arrow (Prod ts) (Prod [x]) = return $ Fun ts x arrow _ _ = fatalError "invalid type" leaf :: Parser Type_ leaf = do { defined DTType; return TType } <|> do { defined DO; return (Prod [O]) } <|> do { ty <- type_; return (Prod [ty]) } <|> parens compoundType compoundType :: Parser Type_ compoundType = leaf `binExpr` (punct Times >> return prod) `binExpr` (punct FunArrow >> return arrow) typeDeclaration :: Parser () typeDeclaration = do keyword L.Type punct Comma let manyParens p = parens (manyParens p) <|> p manyParens $ do name <- atom punct Colon res <- compoundType case res of TType -> do { findType name; return () } Fun args res -> do { newFunction name (FunType args res); return () } Prod [res] -> do { newFunction name (FunType [] res); return () } _ -> fatalError "invalid type"