grammatical-parsers-0.7: examples/BooleanTransformations.hs
{-# LANGUAGE FlexibleContexts, FlexibleInstances, KindSignatures, RankNTypes, RecordWildCards,
StandaloneDeriving, UndecidableInstances #-}
module Main where
import Control.Applicative ((<|>), empty)
import Control.Arrow (first)
import Control.Monad (guard, join)
import Data.Char (isSpace)
import Data.List (isPrefixOf)
import Data.Functor.Compose (Compose(..))
import Data.Functor.Identity (Identity(..))
import Data.Maybe (mapMaybe)
import Data.Semigroup ((<>))
import System.Environment (getArgs)
import Text.Parser.Token (TokenParsing(..), symbol)
import qualified Text.Grampa
import Text.Grampa (TokenParsing(someSpace), LexicalParsing(lexicalComment, lexicalWhiteSpace, someLexicalSpace),
GrammarBuilder, ParseResults,
fixGrammar, parseComplete,
char, identifier, keyword, takeCharsWhile)
import Text.Grampa.ContextFree.SortedMemoizing.Transformer.LeftRecursive (ParserT, lift, tmap)
import qualified Boolean
import Boolean(Boolean(..))
-- |
-- >>> simplifiedSource "True && [comment] x"
-- "[comment] x"
-- >>> simplifiedSource "False || [comment1] (True || [comment2] x)"
-- "[comment1] True "
-- >>> simplifiedSource "False || [^ trailing comment] [leading comment] (True || [comment2] x)"
-- "[leading comment] True "
-- >>> simplifiedSource "False || [^ trailing comment] [leading comment] (True [operator leading] || [comment2] x)"
-- "[leading comment] True "
-- >>> simplifiedSource "([^1][1] True [^2][2] && [^3][3] x [^4][4])[^5][5] || [^6][6] False [^7][7]"
-- "[3] x [^4][^5]"
type Parser = ParserT ((,) [Ignorables])
data AST f = And (f (AST f)) (f (AST f))
| Or (f (AST f)) (f (AST f))
| Not (f (AST f))
| Literal Bool
| Variable String
deriving instance (Show (f (AST f)), Show (f Bool), Show (f String)) => Show (AST f)
instance Boolean.BooleanDomain (ParsedWrap (AST ParsedWrap)) where
and = binary And
or = binary Or
not = bare . Not
true = bare (Literal True)
false = bare (Literal False)
binary :: (ParsedWrap (AST ParsedWrap) -> ParsedWrap (AST ParsedWrap) -> AST ParsedWrap)
-> ParsedWrap (AST ParsedWrap) -> ParsedWrap (AST ParsedWrap) -> ParsedWrap (AST ParsedWrap)
binary f a b = bare (f a b)
type ParsedWrap = (,) ParsedIgnorables
type NodeWrap = (,) AttachedIgnorables
data AttachedIgnorables = Attached Ignorables AttachedIgnorables Ignorables
| Blank
| AttachedToOperator Ignorables Ignorables
| Parenthesized Ignorables AttachedIgnorables Ignorables
deriving Show
data ParsedIgnorables = Trailing Ignorables
| OperatorTrailing [Ignorables]
| ParenthesesTrailing Ignorables ParsedIgnorables Ignorables
deriving Show
type Ignorables = [Either WhiteSpace Comment]
newtype Comment = Comment{getComment :: String} deriving Show
newtype WhiteSpace = WhiteSpace String deriving Show
type Grammar = Boolean.Boolean (ParsedWrap (AST ParsedWrap))
main :: IO ()
main = do args <- concat <$> getArgs
let tree = parse args
print tree
case tree
of Right [parsed] -> do let rearranged = completeRearranged mempty parsed
print rearranged
putStrLn (showSource $ simplified rearranged)
other -> error (show other)
parse :: String -> ParseResults String [ParsedWrap (AST ParsedWrap)]
parse = getCompose . fmap snd . getCompose . Boolean.expr . parseComplete (fixGrammar grammar)
simplifiedSource :: String -> String
simplifiedSource input = case (parse input)
of Right [parsed] -> showSource (simplified $ completeRearranged mempty parsed)
other -> error (show other)
class ShowSource a where
showSource :: a -> String
showsSourcePrec :: Int -> a -> String -> String
showSource a = showsSourcePrec 0 a mempty
instance ShowSource (NodeWrap (AST NodeWrap)) where
showsSourcePrec prec (Attached lead Blank follow, node) rest =
whiteString lead <> showsSourcePrec prec node (whiteString follow <> rest)
showsSourcePrec prec (Parenthesized lead ws follow, node) rest =
whiteString lead <> showsSourcePrec 0 (ws, node) (whiteString follow <> rest)
instance ShowSource (AST NodeWrap) where
showsSourcePrec prec (Or left right) rest
| prec < 1 = showsSourcePrec 1 left ("||" <> showsSourcePrec 0 right rest)
showsSourcePrec prec (And left right) rest
| prec < 2 = showsSourcePrec 2 left ("&&" <> showsSourcePrec 1 right rest)
showsSourcePrec prec (Not expr) rest
| prec < 3 = "not" <> showsSourcePrec 2 expr rest
showsSourcePrec _ (Literal True) rest = "True" <> rest
showsSourcePrec _ (Literal False) rest = "False" <> rest
showsSourcePrec _ (Variable name) rest = name <> rest
showsSourcePrec _ node rest = "(" <> showsSourcePrec 0 node (")" <> rest)
completeRearranged :: Ignorables -> ParsedWrap (AST ParsedWrap) -> NodeWrap (AST NodeWrap)
completeRearranged ws node
| ((ws', node'), trailing) <- rearranged ws node = (embed [] ws' trailing, node')
rearranged :: Ignorables -> ParsedWrap (AST ParsedWrap) -> (NodeWrap (AST NodeWrap), Ignorables)
rearranged leftover (Trailing follow, node)
| (follow', lead') <- splitDirections follow, (lead'', node', follow'') <- rearrangedChildren [] node =
((Attached (leftover <> lead'') Blank (follow'' <> follow'), node'), lead')
rearranged leftover (OperatorTrailing [[], follow], node)
| (follow', lead') <- splitDirections follow,
(lead'', node', follow'') <- rearrangedChildren [[], lead'] node =
((Attached leftover (AttachedToOperator lead'' follow') [], node'), follow'')
rearranged leftover (ParenthesesTrailing lead ws follow, node)
| (follow', lead') <- splitDirections follow, (ws', node') <- completeRearranged lead (ws, node) =
((Parenthesized leftover ws' follow', node'), lead')
embed leading Blank trailing = Attached leading Blank trailing
embed leading (Attached lead inside follow) trailing = embed (leading <> lead) inside (follow <> trailing)
embed leading (AttachedToOperator lead follow) trailing = AttachedToOperator (leading <> lead) (follow <> trailing)
embed leading (Parenthesized lead inside follow) trailing = Parenthesized (leading <> lead) inside (follow <> trailing)
rearrangedChildren :: [Ignorables] -> AST ParsedWrap -> (Ignorables, AST NodeWrap, Ignorables)
rearrangedChildren [left, right] (And a b)
| (a', follow1) <- rearranged left a,
(b', follow2) <- rearranged right b = (follow1, And a' b', follow2)
rearrangedChildren [left, right] (Or a b)
| (a', follow1) <- rearranged left a,
(b', follow2) <- rearranged right b = (follow1, Or a' b', follow2)
rearrangedChildren [leftover] (Not a)
| (a', follow) <- rearranged leftover a = ([], Not a', follow)
rearrangedChildren [] (Literal a) = ([], Literal a, [])
rearrangedChildren [] (Variable name) = ([], Variable name, [])
-- | Separates the whitespace and comments that refer to the preceding construct.
splitDirections :: Ignorables -> (Ignorables, Ignorables)
splitDirections = span (either (const True) (isPrefixOf "^" . getComment))
-- | Simplifies the given expression according to the laws of Boolean algebra.
simplified :: NodeWrap (AST NodeWrap) -> NodeWrap (AST NodeWrap)
simplified e@(_, Literal{}) = e
simplified e@(_, Variable{}) = e
simplified (a, Not e) = case simplified e
of (b, Literal True) -> (raise a b, Literal False)
(b, Literal False) -> (raise a b, Literal True)
e' -> (a, Not e')
simplified (a, And l r) = case (simplified l, simplified r)
of ((b, Literal False), _) -> (raise a b, Literal False)
((b, Literal True), (c, r')) -> (raise a c, r')
(_, (b, Literal False)) -> (raise a b, Literal False)
((b, l'), (c, Literal True)) -> (raise a b, l')
(l', r') -> (a, And l' r')
simplified (a, Or l r) = case (simplified l, simplified r)
of ((b, Literal False), (c, r')) -> (raise a c, r')
((b, Literal True), _) -> (raise a b, Literal True)
((b, l'), (c, Literal False)) -> (raise a b, l')
(_, (b, Literal True)) -> (raise a b, Literal True)
(l', r') -> (a, Or l' r')
raise :: AttachedIgnorables -> AttachedIgnorables -> AttachedIgnorables
raise Blank arg = arg
raise op Blank = op
raise AttachedToOperator{} arg = arg
raise (Parenthesized opl op opr) arg = Parenthesized opl (raise op arg) opr
raise (Attached opl inside opr) (Parenthesized l arg r) =
Parenthesized (comments opl <> l) (raise inside arg) (r <> comments opr)
-- raise (AttachedToOperator opl opr) (Parenthesized l arg r) = Parenthesized (comments opl <> l) arg (r <> comments opr)
-- raise (AttachedToOperator opl opr) (Attached argl inside argr) =
-- Attached (comments opl <> argl) inside (argr <> comments opr)
comments :: Ignorables -> Ignorables
comments = mapMaybe (either (const Nothing) (Just . Right))
whiteString :: Ignorables -> String
whiteString (Left (WhiteSpace ws) : rest) = ws <> whiteString rest
whiteString (Right (Comment c) : rest) = "[" <> c <> "]" <> whiteString rest
whiteString [] = ""
grammar :: GrammarBuilder Grammar Grammar Parser String
grammar Boolean{..} = Boolean{
expr= term
<|> operatorTrailingWhiteSpace [mempty] (Boolean.or <$> term <* symbol "||" <*> expr),
term= factor
<|> operatorTrailingWhiteSpace [mempty] (Boolean.and <$> factor <* symbol "&&" <*> term),
factor= trailingWhiteSpace (keyword "True" *> pure Boolean.true
<|> keyword "False" *> pure Boolean.false
<|> bare . Variable <$> identifier)
<|> operatorTrailingWhiteSpace [] (keyword "not" *> (Boolean.not <$> factor))
<|> parenthesizedWhiteSpace (symbol "(" *> expr <* symbol ")")}
bare :: a -> ParsedWrap a
bare a = (Trailing [], a)
operatorTrailingWhiteSpace :: [Ignorables] -> Parser Grammar String (ParsedWrap (AST ParsedWrap))
-> Parser Grammar String (ParsedWrap (AST ParsedWrap))
trailingWhiteSpace, parenthesizedWhiteSpace
:: Parser Grammar String (ParsedWrap (AST ParsedWrap)) -> Parser Grammar String (ParsedWrap (AST ParsedWrap))
trailingWhiteSpace = tmap store
where store ([ws], (Trailing ws', a)) = (mempty, (Trailing $ ws' <> ws, a))
operatorTrailingWhiteSpace prefix = tmap store
where store (wss, (Trailing [], a)) = (mempty, (OperatorTrailing (prefix <> wss), a))
parenthesizedWhiteSpace = tmap store
where store ([ws,ws'], (aws, a)) = ([], (ParenthesesTrailing ws aws ws', a))
instance {-# OVERLAPS #-} TokenParsing (Parser Grammar String) where
someSpace = someLexicalSpace
token p = p <* lexicalWhiteSpace
instance {-# OVERLAPS #-} LexicalParsing (Parser Grammar String) where
lexicalWhiteSpace = tmap (first (\ws-> [concat ws])) $
do ws <- takeCharsWhile isSpace
lift ([[Left $ WhiteSpace ws]], ())
(lexicalComment *> lexicalWhiteSpace <|> pure ())
lexicalComment = do char '['
comment <- takeCharsWhile (/= ']')
char ']'
lift ([[Right $ Comment comment]], ())
identifierToken p = do ident <- p
guard (ident /= "True" && ident /= "False")
lexicalWhiteSpace
pure ident