cg-0.0.9.0: CG/Parsing.hs
{-# LANGUAGE RecordWildCards, RankNTypes, TupleSections, ScopedTypeVariables #-}
module CG.Parsing (readSystem
,readLexicon
,parseGoal
,formula
,structure
,judgement
,getCgHome
)where
import CG.Base
import Control.Exception (IOException, handle)
import Data.Char (isSpace)
import Data.List (nub,isPrefixOf)
import Data.Void (Void)
import Data.Map (Map)
import qualified Data.Map as M
import Text.Parsec hiding (option)
import Text.Parsec.Expr
import Text.Parsec.Error (setErrorPos)
import Text.Parsec.String (Parser)
import Text.Parsec.Language (LanguageDef,haskellStyle)
import Text.Parsec.Token (TokenParser,GenTokenParser(..),GenLanguageDef(..),makeTokenParser)
import System.Directory (doesFileExist)
import System.Environment (getEnv)
import System.FilePath ((</>))
-- * Variable Parsing
class VarParser v where
formulaVar :: Parser v
structureVar :: Parser v
instance VarParser Void where
formulaVar = oneOf "ABCD" *> unexpected "Variables {A,B,C,D} are not allowed."
structureVar = oneOf "XYZW" *> unexpected "Variables {X,Y,Z,W} are not allowed."
instance VarParser Char where
formulaVar = lexeme cg (oneOf "ABCD") <?> "formula variable"
structureVar = lexeme cg (oneOf "XYZW") <?> "structure variable"
-- * Language Definition
cgDef :: LanguageDef st
cgDef = haskellStyle
{ identStart = identLetter cgDef
, identLetter = satisfy (\c -> not (isSpace c || c `elem` "()"))
, opStart = opLetter cgDef
, opLetter = oneOf "()"
, reservedNames = let
reservedNames = ["where","and","atomic","positive","negative"]
reservedOpNames = nub (map toString operators ++ map (toString . ASCII) operators)
operators = concat [unaryLogical ,unaryStructural
,binaryLogical,binaryStructural
,sequents, [Down]]
in reservedNames ++ reservedOpNames
}
cg :: TokenParser st
cg = makeTokenParser cgDef
-- * Term Parsing
con :: ConId -> Parser ()
con c = reserved (toString c) <|> reserved (toString (ASCII c))
where
TokenParser{..} = cg
con' :: ConId -> Parser ()
con' c = do symbol (toString c) <|> symbol (toString (ASCII c)); return ()
where
TokenParser{..} = cg
infl c = Infix (con c *> return (binary c)) AssocLeft
infr c = Infix (con c *> return (binary c)) AssocRight
pref c = Prefix (con c *> return (unary c))
post c = Postfix (con c *> return (unary c))
atom :: Parser (Term ConId v)
atom = do
let TokenParser{..} = cg
x <- identifier
return . nullary $
if last x `elem` "⁻'"
then NegAtom (init x)
else PosAtom x
formulaWithVar :: Parser (Term ConId Char)
formulaWithVar = formula
formula :: VarParser v => Parser (Term ConId v)
formula =
buildExpressionParser table (parens formula <|> fvar <|> atom)
where
TokenParser{..} = cg
fvar = Var <$> formulaVar
table =
[ [ pref F0L , post F0R , pref FBox
, pref F1L , post F1R , pref FDia ]
, [ infl FSubL , infl FImpL , infl HImpL
, infr FSubR , infr FImpR , infr HImpR ]
, [ infr FProd , infl FPlus , infr HProd ]
]
structureWithVar :: Parser (Term ConId Char)
structureWithVar = structure
structure :: VarParser v => Parser (Term ConId v)
structure =
buildExpressionParser table (parens structure <|> sdia <|> sbox <|> svar <|> down)
where
TokenParser{..} = cg
svar = Var <$> structureVar
down = unary Down <$> cdots formula <?> "down operator"
sdia = unary SDia <$> (angles structure <|> rangles structure)
sbox = unary SBox <$> (brackets structure)
cdots = between (con Down) (con Down)
rangles = between (symbol "⟨") (symbol "⟩")
table =
[ [ pref S0L , post S0R , pref S1L , post S1R ]
, [ infl SSubL , infl SImpL , infr SSubR , infr SImpR ]
, [ infr SProd , infl SPlus ]
, [ infr Comma ]
]
judgementWithVar :: Parser (Term ConId Char)
judgementWithVar = judgement
judgement :: VarParser v => Parser (Term ConId v)
judgement =
spaces *> (try (try jstruct <|> (try jfocusl <|> jfocusr)) <|> jalgebr)
where
TokenParser{..} = cg
jstruct = binary JStruct <$> structure <* con' JStruct <*> structure
jfocusl = binary JFocusL <$> brackets formula <* con' JFocusL <*> structure
jfocusr = binary JFocusR <$> structure <* con' JFocusL <*> brackets formula
jalgebr = binary JAlgebr <$> formula <* con' JAlgebr <*> formula
lexicon :: Parser (Map String (Term ConId Void))
lexicon = whiteSpace >> M.fromList <$> many1 entry
where
TokenParser{..} = cg
entry = (,) <$> identifier <* symbol ":" <*> formula
guard' :: Parser (Term ConId Char -> Guard ConId)
guard' =
do reserved "where"
gs <- sepBy1 single (reserved "and")
if null gs
then unexpected "panic!"
else return (foldr1 and gs)
where
TokenParser{..} = cg
and f g x = f x `And` g x
single = predicate <*> formulaVar
predicate = choice
[ reserved "atomic" *> return atomic
, reserved "positive" *> return positive
, reserved "negative" *> return negative
]
rule :: Parser (Rule ConId Int)
rule = do
n <- identifier
_ <- symbol ":"
js <- sepBy1 judgement sep
let ps = init js
let c = last js
let rule = mkRule n ps c
mb <- optionMaybe guard'
return $
case mb of
Just mk -> rule { guard = mk c }
_ -> rule
where
TokenParser{..} = cg
sep = symbol "→" <|> symbol "-->"
option :: Parser Option
option = do
symbol "set"
choice (map try
[ symbol "finite" *> return (IsFinite True)
, symbol "infinite" *> return (IsFinite False)
, symbol "algebraic" *> return (IsStructural False)
, symbol "structural" *> return (IsStructural True)
, symbol "agda_name" *> symbol "=" *> (AgdaName <$> many1 (satisfy (not . isSpace)))
, symbol "agda_module" *> symbol "=" *> (AgdaModule <$> many1 (satisfy (not . isSpace)))
, symbol "parse_with" *> symbol "=" *> (ParseWith <$> pParseWith)
])
<?> "option"
where
TokenParser{..} = cg
pParseWith :: Parser [ParseWith]
pParseWith = many1 (useBinary <|> useUnary)
where
useOp opt = choice . map (\c -> con c >> return (opt c))
useUnary = useOp UseUnary (unaryLogical ++ unaryStructural)
useBinary = useOp UseBinary (binaryLogical ++ binaryStructural)
-- * Options
data Option
= IsFinite Bool
| IsStructural Bool
| ParseWith [ParseWith]
| AgdaName String
| AgdaModule String
deriving Show
data ParseWith
= UseUnary ConId
| UseBinary ConId
deriving Show
-- |Handle options for proof systems.
runOptions :: [Option] -> System ConId
runOptions = postProcess . foldr runOption emptySystem
where
runOption (IsFinite b) s = s { finite = b }
runOption (IsStructural b) s = s { structural = b }
runOption (AgdaName n) s = s { agdaName = Just n }
runOption (AgdaModule n) s = s { agdaModule = Just n }
runOption (ParseWith x) s = foldr runPW s x
runPW (UseUnary op) = addUnary op
runPW (UseBinary op) = addBinary op
postProcess sys@System{..}
| structural = sys { unaryOp = fmap toStructural unaryOp
, binaryOp = fmap toStructural binaryOp }
| otherwise = sys
-- * Parsers
parseGoal :: String -> IO (Term ConId Void)
parseGoal str = case parse formula "" str of
Left e -> fail ("Could not parse goal formula `"++show str++"'.\n"++show e)
Right x -> return x
getCgHome :: IO (Maybe String)
getCgHome =
handle (\(e :: IOException) -> return Nothing) (Just <$> getEnv "CGTOOL_HOME")
readLexicon :: FilePath -> IO (Map String (Term ConId Void))
readLexicon lexiconFile = do
fileExist <- doesFileExist lexiconFile
if fileExist
then unsafeReadLexicon lexiconFile
else do mbHome <- getCgHome
case mbHome of
Just home -> do
let lexiconFile' = home </> "lex" </> lexiconFile
fileExist' <- doesFileExist lexiconFile'
if fileExist'
then unsafeReadLexicon lexiconFile'
else error ("No such lexicon `" ++ lexiconFile ++ "'")
Nothing -> error ("No such lexicon `" ++ lexiconFile ++ "'")
unsafeReadLexicon :: FilePath -> IO (Map String (Term ConId Void))
unsafeReadLexicon lexiconFile = do
lexiconContent <- readFile lexiconFile
case parse lexicon lexiconFile lexiconContent of
Left e -> fail ("Could not parse lexicon.\n"++show e)
Right l -> return l
readSystem :: FilePath -> IO (System ConId)
readSystem systemFile = do
fileExist <- doesFileExist systemFile
if fileExist
then unsafeReadSystem systemFile
else do mbHome <- getCgHome
case mbHome of
Just home -> do
let systemFile' = home </> "sys" </> systemFile
fileExist' <- doesFileExist systemFile'
if fileExist'
then unsafeReadSystem systemFile'
else error ("No such system `" ++ systemFile ++ "'")
Nothing -> error ("No such system `" ++ systemFile ++ "'")
unsafeReadSystem :: FilePath -> IO (System ConId)
unsafeReadSystem systemFile = go <$> readFile systemFile
where
go contents = sys { rules = rules }
where
TokenParser{..} = cg
(rawOpts,rawRules)
= span (isOption . snd) -- break at first non-option
$ filter (not . isComment . snd) -- filter out all comments
$ zip [1..] -- add line numbers
$ lines contents -- split lines
opts = map (parseOrError option systemFile) rawOpts
rules = map (parseOrError rule systemFile) rawRules
sys = runOptions opts
isOption ln = "set" `isPrefixOf` ln
isComment ln = case parse (whiteSpace *> eof) "" ln of
Left _ -> False
Right _ -> True
parseOrError :: Parser a -> FilePath -> (Line, String) -> a
parseOrError p fn (n,str) = case parse p fn str of
Left e -> error (show (setErrorPos (setSourceLine (errorPos e) n) e))
Right x -> x