feat(formula): add propositional formula parser and evaluator
This commit is contained in:
1 parent
09cf81b3e0
commit
a0cdd0c32b
1 file changed
+157
@@ -0,0 +1,157 @@
|
|||||||
|
module Sift.Formula
|
||||||
|
( Name
|
||||||
|
, Formula(..)
|
||||||
|
, atoms
|
||||||
|
, evalFormula
|
||||||
|
, parseFormula
|
||||||
|
) where
|
||||||
|
|
||||||
|
import Data.Char (isAlpha, isAlphaNum, isSpace)
|
||||||
|
import Data.List (nub)
|
||||||
|
import Data.Maybe (fromMaybe)
|
||||||
|
import Data.Map.Strict (Map)
|
||||||
|
import qualified Data.Map.Strict as Map
|
||||||
|
|
||||||
|
type Name = String
|
||||||
|
|
||||||
|
data Formula
|
||||||
|
= Atom Name
|
||||||
|
| Const Bool
|
||||||
|
| Not Formula
|
||||||
|
| And Formula Formula
|
||||||
|
| Or Formula Formula
|
||||||
|
| Imp Formula Formula
|
||||||
|
| Iff Formula Formula
|
||||||
|
deriving (Eq, Show)
|
||||||
|
|
||||||
|
atoms :: Formula -> [Name]
|
||||||
|
atoms = nub . collect
|
||||||
|
where
|
||||||
|
collect (Atom n) = [n]
|
||||||
|
collect (Const _) = []
|
||||||
|
collect (Not a) = collect a
|
||||||
|
collect (And a b) = collect a ++ collect b
|
||||||
|
collect (Or a b) = collect a ++ collect b
|
||||||
|
collect (Imp a b) = collect a ++ collect b
|
||||||
|
collect (Iff a b) = collect a ++ collect b
|
||||||
|
|
||||||
|
evalFormula :: Map Name Bool -> Formula -> Bool
|
||||||
|
evalFormula env = go
|
||||||
|
where
|
||||||
|
go (Atom n) = fromMaybe (error ("unbound atom: " ++ n)) (Map.lookup n env)
|
||||||
|
go (Const b) = b
|
||||||
|
go (Not a) = not (go a)
|
||||||
|
go (And a b) = go a && go b
|
||||||
|
go (Or a b) = go a || go b
|
||||||
|
go (Imp a b) = not (go a) || go b
|
||||||
|
go (Iff a b) = go a == go b
|
||||||
|
|
||||||
|
data Token
|
||||||
|
= TLParen
|
||||||
|
| TRParen
|
||||||
|
| TNot
|
||||||
|
| TAnd
|
||||||
|
| TOr
|
||||||
|
| TImp
|
||||||
|
| TIff
|
||||||
|
| TTrue
|
||||||
|
| TFalse
|
||||||
|
| TName Name
|
||||||
|
deriving (Eq, Show)
|
||||||
|
|
||||||
|
tokenize :: String -> Either String [Token]
|
||||||
|
tokenize [] = Right []
|
||||||
|
tokenize (c : cs)
|
||||||
|
| isSpace c = tokenize cs
|
||||||
|
| c == '(' = (TLParen :) <$> tokenize cs
|
||||||
|
| c == ')' = (TRParen :) <$> tokenize cs
|
||||||
|
| c == '~' = (TNot :) <$> tokenize cs
|
||||||
|
| c == '!' = (TNot :) <$> tokenize cs
|
||||||
|
| c == '&' = (TAnd :) <$> tokenize cs
|
||||||
|
| c == '|' = (TOr :) <$> tokenize cs
|
||||||
|
| c == '-', ('>' : rest) <- cs = (TImp :) <$> tokenize rest
|
||||||
|
| c == '=', ('>' : rest) <- cs = (TImp :) <$> tokenize rest
|
||||||
|
| c == '<', ('-' : '>' : rest) <- cs = (TIff :) <$> tokenize rest
|
||||||
|
| c == '<', ('=' : '>' : rest) <- cs = (TIff :) <$> tokenize rest
|
||||||
|
| isAlpha c =
|
||||||
|
let (word, rest) = span isIdentChar (c : cs)
|
||||||
|
in (keyword word :) <$> tokenize rest
|
||||||
|
| otherwise = Left ("unexpected character: " ++ show c)
|
||||||
|
|
||||||
|
isIdentChar :: Char -> Bool
|
||||||
|
isIdentChar ch = isAlphaNum ch || ch == '_'
|
||||||
|
|
||||||
|
keyword :: String -> Token
|
||||||
|
keyword word = case word of
|
||||||
|
"true" -> TTrue
|
||||||
|
"false" -> TFalse
|
||||||
|
"not" -> TNot
|
||||||
|
"and" -> TAnd
|
||||||
|
"or" -> TOr
|
||||||
|
_ -> TName word
|
||||||
|
|
||||||
|
parseFormula :: String -> Either String Formula
|
||||||
|
parseFormula src = do
|
||||||
|
toks <- tokenize src
|
||||||
|
(formula, rest) <- pIff toks
|
||||||
|
case rest of
|
||||||
|
[] -> Right formula
|
||||||
|
_ -> Left ("unexpected trailing tokens: " ++ show rest)
|
||||||
|
|
||||||
|
type Parse = [Token] -> Either String (Formula, [Token])
|
||||||
|
|
||||||
|
pIff :: Parse
|
||||||
|
pIff ts = do
|
||||||
|
(left, r1) <- pImp ts
|
||||||
|
chain left r1
|
||||||
|
where
|
||||||
|
chain left (TIff : rest) = do
|
||||||
|
(right, r2) <- pImp rest
|
||||||
|
chain (Iff left right) r2
|
||||||
|
chain left rest = Right (left, rest)
|
||||||
|
|
||||||
|
pImp :: Parse
|
||||||
|
pImp ts = do
|
||||||
|
(left, r1) <- pOr ts
|
||||||
|
case r1 of
|
||||||
|
(TImp : rest) -> do
|
||||||
|
(right, r2) <- pImp rest
|
||||||
|
Right (Imp left right, r2)
|
||||||
|
_ -> Right (left, r1)
|
||||||
|
|
||||||
|
pOr :: Parse
|
||||||
|
pOr ts = do
|
||||||
|
(left, r1) <- pAnd ts
|
||||||
|
chain left r1
|
||||||
|
where
|
||||||
|
chain left (TOr : rest) = do
|
||||||
|
(right, r2) <- pAnd rest
|
||||||
|
chain (Or left right) r2
|
||||||
|
chain left rest = Right (left, rest)
|
||||||
|
|
||||||
|
pAnd :: Parse
|
||||||
|
pAnd ts = do
|
||||||
|
(left, r1) <- pNot ts
|
||||||
|
chain left r1
|
||||||
|
where
|
||||||
|
chain left (TAnd : rest) = do
|
||||||
|
(right, r2) <- pNot rest
|
||||||
|
chain (And left right) r2
|
||||||
|
chain left rest = Right (left, rest)
|
||||||
|
|
||||||
|
pNot :: Parse
|
||||||
|
pNot (TNot : rest) = do
|
||||||
|
(formula, r) <- pNot rest
|
||||||
|
Right (Not formula, r)
|
||||||
|
pNot ts = pAtom ts
|
||||||
|
|
||||||
|
pAtom :: Parse
|
||||||
|
pAtom (TLParen : rest) = do
|
||||||
|
(formula, r1) <- pIff rest
|
||||||
|
case r1 of
|
||||||
|
(TRParen : r2) -> Right (formula, r2)
|
||||||
|
_ -> Left "expected closing parenthesis"
|
||||||
|
pAtom (TTrue : rest) = Right (Const True, rest)
|
||||||
|
pAtom (TFalse : rest) = Right (Const False, rest)
|
||||||
|
pAtom (TName n : rest) = Right (Atom n, rest)
|
||||||
|
pAtom ts = Left ("expected atom, got: " ++ show ts)
|
||||||
Reference in new issue
Block a user