2011-09-18 20:02:12 +00:00
|
|
|
{- A generic matcher.
|
|
|
|
-
|
|
|
|
- Can be used to check if a user-supplied condition,
|
|
|
|
- like "foo and ( bar or not baz )" matches. The condition must already
|
|
|
|
- be tokenized, and can contain arbitrary operations.
|
|
|
|
-
|
|
|
|
- If operations are not separated by and/or, they are defaulted to being
|
|
|
|
- anded together, so "foo bar baz" all must match.
|
|
|
|
-
|
|
|
|
- Is forgiving about misplaced closing parens, so "foo and (bar or baz"
|
|
|
|
- will be handled, as will "foo and ( bar or baz ) )"
|
|
|
|
-
|
2013-05-25 01:33:54 +00:00
|
|
|
- Copyright 2011-2013 Joey Hess <joey@kitenet.net>
|
2011-09-18 20:02:12 +00:00
|
|
|
-
|
2014-05-10 14:01:27 +00:00
|
|
|
- License: BSD-2-clause
|
2011-09-18 20:02:12 +00:00
|
|
|
-}
|
|
|
|
|
2012-10-13 19:17:15 +00:00
|
|
|
{-# LANGUAGE Rank2Types, KindSignatures #-}
|
|
|
|
|
2011-09-18 20:36:30 +00:00
|
|
|
module Utility.Matcher (
|
2011-09-18 20:02:12 +00:00
|
|
|
Token(..),
|
2014-03-29 19:20:55 +00:00
|
|
|
Matcher(..),
|
2011-09-20 04:49:40 +00:00
|
|
|
token,
|
2012-10-04 19:48:59 +00:00
|
|
|
tokens,
|
2011-09-18 20:32:39 +00:00
|
|
|
generate,
|
2011-09-18 20:02:12 +00:00
|
|
|
match,
|
2011-09-19 00:41:51 +00:00
|
|
|
matchM,
|
2012-10-13 19:17:15 +00:00
|
|
|
matchMrun,
|
2013-05-25 01:33:54 +00:00
|
|
|
isEmpty,
|
|
|
|
|
|
|
|
prop_matcher_sane
|
2011-09-18 20:02:12 +00:00
|
|
|
) where
|
|
|
|
|
2012-03-16 16:28:17 +00:00
|
|
|
import Common
|
2011-09-18 20:02:12 +00:00
|
|
|
|
2011-09-20 04:49:40 +00:00
|
|
|
{- A Token can be an Operation of an arbitrary type, or one of a few
|
|
|
|
- predefined peices of syntax. -}
|
|
|
|
data Token op = Operation op | And | Or | Not | Open | Close
|
2011-09-18 20:02:12 +00:00
|
|
|
deriving (Show, Eq)
|
|
|
|
|
2011-09-20 04:49:40 +00:00
|
|
|
data Matcher op = MAny
|
|
|
|
| MAnd (Matcher op) (Matcher op)
|
|
|
|
| MOr (Matcher op) (Matcher op)
|
|
|
|
| MNot (Matcher op)
|
|
|
|
| MOp op
|
2011-09-18 20:02:12 +00:00
|
|
|
deriving (Show, Eq)
|
|
|
|
|
2011-09-20 04:49:40 +00:00
|
|
|
{- Converts a word of syntax into a token. Doesn't handle operations. -}
|
|
|
|
token :: String -> Token op
|
|
|
|
token "and" = And
|
|
|
|
token "or" = Or
|
|
|
|
token "not" = Not
|
|
|
|
token "(" = Open
|
|
|
|
token ")" = Close
|
|
|
|
token t = error $ "unknown token " ++ t
|
|
|
|
|
2012-10-04 19:48:59 +00:00
|
|
|
tokens :: [String]
|
|
|
|
tokens = words "and or not ( )"
|
|
|
|
|
2011-09-18 20:02:12 +00:00
|
|
|
{- Converts a list of Tokens into a Matcher. -}
|
2011-09-18 20:32:39 +00:00
|
|
|
generate :: [Token op] -> Matcher op
|
2013-05-25 01:33:54 +00:00
|
|
|
generate = simplify . process MAny . tokenGroups
|
2012-12-13 04:24:19 +00:00
|
|
|
where
|
2013-05-25 01:33:54 +00:00
|
|
|
process m [] = m
|
|
|
|
process m ts = uncurry process $ consume m ts
|
|
|
|
|
2014-04-26 23:25:05 +00:00
|
|
|
consume m (One And:rest) = term (m `MAnd`) rest
|
|
|
|
consume m (One Or:rest) = term (m `MOr`) rest
|
|
|
|
consume m (One Not:rest) = term (\p -> m `MAnd` (MNot p)) rest
|
|
|
|
consume m (One (Operation o):rest) = (m `MAnd` MOp o, rest)
|
2013-05-25 01:33:54 +00:00
|
|
|
consume m (Group g:rest) = (process m g, rest)
|
|
|
|
consume m (_:rest) = consume m rest
|
|
|
|
consume m [] = (m, [])
|
|
|
|
|
2013-05-25 17:50:27 +00:00
|
|
|
term a l =
|
|
|
|
let (p, l') = consume MAny l
|
|
|
|
in (a p, l')
|
|
|
|
|
2013-05-25 01:33:54 +00:00
|
|
|
simplify (MAnd MAny x) = simplify x
|
|
|
|
simplify (MAnd x MAny) = simplify x
|
|
|
|
simplify (MAnd x y) = MAnd (simplify x) (simplify y)
|
|
|
|
simplify (MOr x y) = MOr (simplify x) (simplify y)
|
|
|
|
simplify (MNot x) = MNot (simplify x)
|
|
|
|
simplify x = x
|
|
|
|
|
|
|
|
data TokenGroup op = One (Token op) | Group [TokenGroup op]
|
|
|
|
deriving (Show, Eq)
|
|
|
|
|
|
|
|
tokenGroups :: [Token op] -> [TokenGroup op]
|
|
|
|
tokenGroups [] = []
|
|
|
|
tokenGroups (t:ts) = go t
|
2012-12-13 04:24:19 +00:00
|
|
|
where
|
2013-05-25 01:33:54 +00:00
|
|
|
go Open =
|
|
|
|
let (gr, rest) = findClose ts
|
|
|
|
in gr : tokenGroups rest
|
|
|
|
go Close = tokenGroups ts -- not picky about missing Close
|
|
|
|
go _ = One t : tokenGroups ts
|
2011-09-20 04:49:40 +00:00
|
|
|
|
2013-05-25 01:33:54 +00:00
|
|
|
findClose :: [Token op] -> (TokenGroup op, [Token op])
|
|
|
|
findClose l =
|
|
|
|
let (g, rest) = go [] l
|
|
|
|
in (Group (reverse g), rest)
|
|
|
|
where
|
|
|
|
go c [] = (c, []) -- not picky about extra Close
|
unify exception handling into Utility.Exception
Removed old extensible-exceptions, only needed for very old ghc.
Made webdav use Utility.Exception, to work after some changes in DAV's
exception handling.
Removed Annex.Exception. Mostly this was trivial, but note that
tryAnnex is replaced with tryNonAsync and catchAnnex replaced with
catchNonAsync. In theory that could be a behavior change, since the former
caught all exceptions, and the latter don't catch async exceptions.
However, in practice, nothing in the Annex monad uses async exceptions.
Grepping for throwTo and killThread only find stuff in the assistant,
which does not seem related.
Command.Add.undo is changed to accept a SomeException, and things
that use it for rollback now catch non-async exceptions, rather than
only IOExceptions.
2014-08-08 01:55:44 +00:00
|
|
|
go c (t:ts) = dispatch t
|
2013-05-25 01:33:54 +00:00
|
|
|
where
|
unify exception handling into Utility.Exception
Removed old extensible-exceptions, only needed for very old ghc.
Made webdav use Utility.Exception, to work after some changes in DAV's
exception handling.
Removed Annex.Exception. Mostly this was trivial, but note that
tryAnnex is replaced with tryNonAsync and catchAnnex replaced with
catchNonAsync. In theory that could be a behavior change, since the former
caught all exceptions, and the latter don't catch async exceptions.
However, in practice, nothing in the Annex monad uses async exceptions.
Grepping for throwTo and killThread only find stuff in the assistant,
which does not seem related.
Command.Add.undo is changed to accept a SomeException, and things
that use it for rollback now catch non-async exceptions, rather than
only IOExceptions.
2014-08-08 01:55:44 +00:00
|
|
|
dispatch Close = (c, ts)
|
|
|
|
dispatch Open =
|
2013-05-25 01:33:54 +00:00
|
|
|
let (c', ts') = go [] ts
|
|
|
|
in go (Group (reverse c') : c) ts'
|
unify exception handling into Utility.Exception
Removed old extensible-exceptions, only needed for very old ghc.
Made webdav use Utility.Exception, to work after some changes in DAV's
exception handling.
Removed Annex.Exception. Mostly this was trivial, but note that
tryAnnex is replaced with tryNonAsync and catchAnnex replaced with
catchNonAsync. In theory that could be a behavior change, since the former
caught all exceptions, and the latter don't catch async exceptions.
However, in practice, nothing in the Annex monad uses async exceptions.
Grepping for throwTo and killThread only find stuff in the assistant,
which does not seem related.
Command.Add.undo is changed to accept a SomeException, and things
that use it for rollback now catch non-async exceptions, rather than
only IOExceptions.
2014-08-08 01:55:44 +00:00
|
|
|
dispatch _ = go (One t:c) ts
|
2011-09-18 20:02:12 +00:00
|
|
|
|
|
|
|
{- Checks if a Matcher matches, using a supplied function to check
|
|
|
|
- the value of Operations. -}
|
2011-09-18 21:47:24 +00:00
|
|
|
match :: (op -> v -> Bool) -> Matcher op -> v -> Bool
|
|
|
|
match a m v = go m
|
2012-12-13 04:24:19 +00:00
|
|
|
where
|
|
|
|
go MAny = True
|
|
|
|
go (MAnd m1 m2) = go m1 && go m2
|
|
|
|
go (MOr m1 m2) = go m1 || go m2
|
|
|
|
go (MNot m1) = not $ go m1
|
|
|
|
go (MOp o) = a o v
|
2011-09-18 20:02:12 +00:00
|
|
|
|
2011-09-18 20:32:39 +00:00
|
|
|
{- Runs a monadic Matcher, where Operations are actions in the monad. -}
|
2011-09-18 21:47:24 +00:00
|
|
|
matchM :: Monad m => Matcher (v -> m Bool) -> v -> m Bool
|
2012-10-13 19:17:15 +00:00
|
|
|
matchM m v = matchMrun m $ \o -> o v
|
2011-09-19 00:41:51 +00:00
|
|
|
|
2012-10-13 19:17:15 +00:00
|
|
|
{- More generic running of a monadic Matcher, with full control over running
|
|
|
|
- of Operations. Mostly useful in order to match on more than one
|
|
|
|
- parameter. -}
|
|
|
|
matchMrun :: forall o (m :: * -> *). Monad m => Matcher o -> (o -> m Bool) -> m Bool
|
|
|
|
matchMrun m run = go m
|
2012-12-13 04:24:19 +00:00
|
|
|
where
|
|
|
|
go MAny = return True
|
|
|
|
go (MAnd m1 m2) = go m1 <&&> go m2
|
|
|
|
go (MOr m1 m2) = go m1 <||> go m2
|
|
|
|
go (MNot m1) = liftM not (go m1)
|
|
|
|
go (MOp o) = run o
|
2012-10-08 17:16:53 +00:00
|
|
|
|
2012-12-06 17:22:16 +00:00
|
|
|
{- Checks if a matcher contains no limits. -}
|
|
|
|
isEmpty :: Matcher a -> Bool
|
|
|
|
isEmpty MAny = True
|
|
|
|
isEmpty _ = False
|
2013-05-25 01:33:54 +00:00
|
|
|
|
|
|
|
prop_matcher_sane :: Bool
|
|
|
|
prop_matcher_sane = all (\m -> match dummy m ()) $ map generate
|
|
|
|
[ [Operation True]
|
|
|
|
, []
|
|
|
|
, [Operation False, Or, Operation True, Or, Operation False]
|
|
|
|
, [Operation True, Or, Operation True]
|
|
|
|
, [Operation True, And, Operation True]
|
|
|
|
, [Not, Open, Operation True, And, Operation False, Close]
|
|
|
|
, [Not, Open, Not, Open, Not, Operation False, Close, Close]
|
|
|
|
, [Not, Open, Not, Open, Not, Open, Not, Operation True, Close, Close]
|
2013-05-25 17:50:27 +00:00
|
|
|
, [Operation True, And, Not, Operation False]
|
|
|
|
, [Operation True, Not, Operation False]
|
|
|
|
, [Operation True, Not, Not, Not, Operation False]
|
|
|
|
, [Operation True, Not, Not, Not, Operation False, And, Operation True]
|
|
|
|
, [Operation True, Not, Not, Not, Operation False, Operation True]
|
|
|
|
, [Not, Open, Operation True, And, Operation False, Close,
|
|
|
|
And, Open,
|
|
|
|
Open, Operation True, And, Operation False, Close,
|
|
|
|
Or,
|
|
|
|
Open, Operation True, And, Open, Not, Operation False, Close, Close,
|
|
|
|
Close, And,
|
|
|
|
Open, Not, Operation False, Close]
|
2013-05-25 01:33:54 +00:00
|
|
|
]
|
|
|
|
where
|
|
|
|
dummy b _ = b
|