ideas-0.7: src/Domain/Logic/Parser.hs
-----------------------------------------------------------------------------
-- Copyright 2010, Open Universiteit Nederland. This file is distributed
-- under the terms of the GNU General Public License. For more information,
-- see the file "LICENSE.txt", which is included in the distribution.
-----------------------------------------------------------------------------
-- |
-- Maintainer : bastiaan.heeren@ou.nl
-- Stability : provisional
-- Portability : portable (depends on ghc)
--
-----------------------------------------------------------------------------
module Domain.Logic.Parser
( parseLogic, parseLogicPars, parseLogicUnicodePars, parseLogicProof
, ppLogicPars, ppLogicUnicodePars
) where
import Common.Utils (ShowString(..))
import Control.Monad.Error (liftM2)
import Text.Parsing
import Control.Arrow
import Domain.Logic.Formula
logicScanner :: Scanner
logicScanner = (specialSymbols "~" defaultScanner)
{ keywords = ["T", "F"]
, keywordOperators = "~" : concatMap (map fst . snd) operatorTable
}
logicUnicodeScanner :: Scanner
logicUnicodeScanner = (specialSymbols (concat unicodeSyms) defaultScanner)
{ keywords = ["T", "F"]
, keywordOperators = unicodeSyms
}
operatorTable :: OperatorTable SLogic
operatorTable =
[ (RightAssociative, [("<->", (:<->:))])
, (RightAssociative, [("||", (:||:))])
, (RightAssociative, [("/\\", (:&&:))])
, (RightAssociative, [("->", (:->:))])
]
-----------------------------------------------------------
--- Parser
-- | Parser for logic formulas that respects all associativity and priority laws
-- | of the constructors
parseLogic :: String -> Either String SLogic
parseLogic = analyseAndParse pLogic . scanWith logicScanner
where
pLogic = pOperators operatorTable (basicWithPos pLogic)
-- | Parser for logic formulas that insists on more parentheses: "and" and "or" are associative,
-- | but implication and equivalence are not. Priorities of the operators are unknown, and thus
-- | parentheses have to be written explicitly. No parentheses are needed for Not (Not p). Superfluous
-- | parentheses are permitted
parseLogicPars :: String -> Either String SLogic
parseLogicPars s
= either Left suspiciousVariable
$ left (ambiguousOperators parseLogic s)
$ analyseAndParse (pLogicGen asciiTuple)
$ scanWith logicScanner s
parseLogicUnicodePars :: String -> Either String SLogic
parseLogicUnicodePars s
= either Left suspiciousVariable
$ left (ambiguousOperators (parseLogic . concatMap f) s)
$ analyseAndParse (pLogicGen unicodeTuple)
$ scanWith logicUnicodeScanner s
where
-- quick fix (since we only need to know whether the parser succeeds)
f c | [c] == andUSym = andASym
| [c] == orUSym = orASym
| [c] == notUSym = notASym
| [c] == implUSym = implASym
| [c] == equivUSym = equivASym
| otherwise = [c]
pLogicGen :: SymbolTuple -> TokenParser SLogic
pLogicGen (impl, equiv, conj, disj, neg, tr, fl) = pLogic
where
pLogic = flip ($) <$> basic <*> optional composed id
basic = basicWithPosGen (neg, tr, fl) pLogic
composed = flip (:<->:) <$ pKey equiv <*> basic
<|> flip (:->:) <$ pKey impl <*> basic
<|> (\xs p -> foldr1 (:&&:) (p:xs)) <$> pList1 (pKey conj *> basic)
<|> (\xs p -> foldr1 (:||:) (p:xs)) <$> pList1 (pKey disj *> basic)
basicWithPos :: TokenParser SLogic -> TokenParser SLogic
basicWithPos = basicWithPosGen ("~", "T", "F")
basicWithPosGen :: (String, String, String) -> TokenParser SLogic -> TokenParser SLogic
basicWithPosGen t@(nt, tr, fl) p =
(Var . ShowString) <$> pVarid
<|> pParens p
<|> T <$ pKey tr
<|> F <$ pKey fl
<|> Not <$ pKey nt <*> basicWithPosGen t p
parseLogicProof :: String -> Either String (SLogic, SLogic)
parseLogicProof s
= either Left susp
$ left (ambiguousOperators parseLogic s)
$ analyseAndParse pProof
$ scanWith extScanner s
where
pProof = (,) <$> pLogicGen asciiTuple <* pKey "==" <*> pLogicGen asciiTuple
susp (p, q) = liftM2 (,) (suspiciousVariable p) (suspiciousVariable q)
extScanner = logicScanner
{keywordOperators = "==" : keywordOperators logicScanner}
-----------------------------------------------------------
--- Helper-functions for syntax warnings
-- analyze parentheses
analyseAndParse :: TokenParser a -> [Token] -> Either String a
analyseAndParse p ts =
case checkParentheses ts of
Just err -> Left (show err)
Nothing -> either (Left . f) Right (parse p ts)
where
f (Just t) = show (tokenPosition t) ++ ": Unexpected " ++ show t
f Nothing = "Syntax error"
ambiguousOperators :: (String -> Either a b) -> String -> String -> String
ambiguousOperators p s err =
let msg = "Ambiguous use of operators (write parentheses)"
in either (const err) (const msg) (p s)
-- Report variables
suspiciousVariable :: SLogic -> Either String SLogic
suspiciousVariable r =
case filter p (map fromShowString (varsLogic r)) of
v:_ -> Left $ "Unexpected variable " ++ v
++ ". Did you forget an operator?"
_ -> Right r
where
p xs = length xs > 1 && all (`elem` "pqrst") xs
-----------------------------------------------------------
--- Pretty-Printer
-- | Pretty printer that produces extra parentheses: also see parseLogicPars
ppLogicPars :: SLogic -> String
ppLogicPars = ppLogicParsGen asciiTuple
-- | Pretty printer with unicode characters
ppLogicUnicodePars :: SLogic -> String
ppLogicUnicodePars = ppLogicParsGen unicodeTuple
ppLogicParsGen :: SymbolTuple -> SLogic -> String
ppLogicParsGen (impl, equiv, conj, disj, neg, tr, fl) =
(\f -> f 0 "") . foldLogic alg
where
alg = ( pp . fromShowString, binop 3 impl, binop 3 equiv, binop 1 conj
, binop 2 disj, nott, pp tr, pp fl
)
binop :: Int -> String -> (Int -> String -> String) -> (Int -> String -> String) -> Int -> String -> String
binop prio op p q n =
parIf (n/=0 && (n==3 || prio/=n))
(p prio . ((" "++op++" ")++) . q prio)
pp s = const (s++)
nott p _ = (neg++) . p 3
parIf b f = if b then ("("++) . f . (")"++) else f
-----------------------------------------------------------
--- Ascii symbols
type SymbolTuple = (String, String, String, String, String, String, String)
asciiTuple :: SymbolTuple
asciiTuple = (implASym, equivASym, andASym, orASym, notASym, "T", "F")
implASym, equivASym, andASym, orASym, notASym :: String
implASym = "->"
equivASym = "<->"
andASym = "/\\"
orASym = "||"
notASym = "~"
-----------------------------------------------------------
--- Unicode symbols
unicodeSyms :: [String]
unicodeSyms = [implUSym, equivUSym, andUSym, orUSym, notUSym]
unicodeTuple :: SymbolTuple
unicodeTuple = (implUSym, equivUSym, andUSym, orUSym, notUSym, "T", "F")
implUSym, equivUSym, andUSym, orUSym, notUSym :: String
implUSym = "\8594"
equivUSym = "\8596"
andUSym = "\8743"
orUSym = "\8744"
notUSym = "\172"