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 ) )"
|
|
|
|
-
|
2021-01-28 17:50:38 +00:00
|
|
|
- Copyright 2011-2021 Joey Hess <id@joeyh.name>
|
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
|
|
|
-}
|
|
|
|
|
2020-09-24 17:55:19 +00:00
|
|
|
{-# LANGUAGE Rank2Types, KindSignatures, DeriveFoldable #-}
|
2012-10-13 19:17:15 +00:00
|
|
|
|
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(..),
|
2019-05-14 17:08:51 +00:00
|
|
|
syntaxToken,
|
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,
|
2015-09-15 16:50:14 +00:00
|
|
|
combineMatchers,
|
2020-09-28 17:22:16 +00:00
|
|
|
introspect,
|
2013-05-25 01:33:54 +00:00
|
|
|
|
|
|
|
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
|
|
|
|
2022-06-29 18:11:20 +00:00
|
|
|
import Data.Kind
|
|
|
|
|
2011-09-20 04:49:40 +00:00
|
|
|
{- A Token can be an Operation of an arbitrary type, or one of a few
|
2023-03-14 02:39:16 +00:00
|
|
|
- predefined pieces of syntax. -}
|
2011-09-20 04:49:40 +00:00
|
|
|
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
|
2020-09-24 17:55:19 +00:00
|
|
|
deriving (Show, Eq, Foldable)
|
2011-09-18 20:02:12 +00:00
|
|
|
|
2011-09-20 04:49:40 +00:00
|
|
|
{- Converts a word of syntax into a token. Doesn't handle operations. -}
|
2019-05-14 17:08:51 +00:00
|
|
|
syntaxToken :: String -> Either String (Token op)
|
|
|
|
syntaxToken "and" = Right And
|
|
|
|
syntaxToken "or" = Right Or
|
|
|
|
syntaxToken "not" = Right Not
|
|
|
|
syntaxToken "(" = Right Open
|
|
|
|
syntaxToken ")" = Right Close
|
|
|
|
syntaxToken t = Left $ "unknown token " ++ t
|
2012-10-04 19:48:59 +00:00
|
|
|
|
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
|
2021-01-28 17:50:38 +00:00
|
|
|
generate = simplify . process MAny . implicitAnd . 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
|
2014-10-09 18:53:13 +00:00
|
|
|
go Open =
|
2013-05-25 01:33:54 +00:00
|
|
|
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
|
2014-10-09 18:53:13 +00:00
|
|
|
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
|
|
|
|
2021-01-28 17:50:38 +00:00
|
|
|
implicitAnd :: [TokenGroup op] -> [TokenGroup op]
|
|
|
|
implicitAnd [] = []
|
|
|
|
implicitAnd [v] = [v]
|
|
|
|
implicitAnd (a:b:rest) | need a && need b = a : One And : implicitAnd (b:rest)
|
|
|
|
where
|
|
|
|
need (One (Operation _)) = True
|
|
|
|
need (Group _) = True
|
|
|
|
need _ = False
|
|
|
|
implicitAnd (a:rest) = a : implicitAnd rest
|
|
|
|
|
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. -}
|
2022-06-29 18:11:20 +00:00
|
|
|
matchMrun :: forall o (m :: Type -> Type). Monad m => Matcher o -> (o -> m Bool) -> m Bool
|
2012-10-13 19:17:15 +00:00
|
|
|
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
|
|
|
|
2015-09-15 16:50:14 +00:00
|
|
|
{- Combines two matchers, yielding a matcher that will match anything
|
2015-09-15 17:12:21 +00:00
|
|
|
- both do. But, if one matcher contains no limits, yield the other one. -}
|
2015-09-15 16:50:14 +00:00
|
|
|
combineMatchers :: Matcher a -> Matcher a -> Matcher a
|
|
|
|
combineMatchers a b
|
|
|
|
| isEmpty a = b
|
|
|
|
| isEmpty b = a
|
|
|
|
| otherwise = a `MOr` b
|
|
|
|
|
2020-09-28 17:22:16 +00:00
|
|
|
{- Checks if anything in the matcher meets the condition. -}
|
|
|
|
introspect :: (a -> Bool) -> Matcher a -> Bool
|
|
|
|
introspect = any
|
|
|
|
|
2013-05-25 01:33:54 +00:00
|
|
|
prop_matcher_sane :: Bool
|
2021-01-28 17:50:38 +00:00
|
|
|
prop_matcher_sane = and
|
|
|
|
[ all (\m -> match (\b _ -> b) m ()) (map generate evaltrue)
|
|
|
|
, all (\(x,y) -> generate x == generate y) evalsame
|
2013-05-25 01:33:54 +00:00
|
|
|
]
|
|
|
|
where
|
2021-01-28 17:50:38 +00:00
|
|
|
evaltrue =
|
|
|
|
[ [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]
|
|
|
|
, [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]
|
|
|
|
]
|
|
|
|
evalsame =
|
|
|
|
[
|
|
|
|
( [Operation "foo", Open, Operation "bar", Or, Operation "baz", Close]
|
|
|
|
, [Operation "foo", And, Open, Operation "bar", Or, Operation "baz", Close]
|
|
|
|
)
|
|
|
|
,
|
|
|
|
( [Operation "foo", Not, Open, Operation "bar", Or, Operation "baz", Close]
|
|
|
|
, [Operation "foo", And, Not, Open, Operation "bar", Or, Operation "baz", Close]
|
|
|
|
)
|
|
|
|
,
|
|
|
|
( [Open, Operation "bar", Or, Operation "baz", Close, Operation "foo"]
|
|
|
|
, [Open, Operation "bar", Or, Operation "baz", Close, And, Operation "foo"]
|
|
|
|
)
|
|
|
|
,
|
|
|
|
( [Open, Operation "bar", Or, Operation "baz", Close, Not, Operation "foo"]
|
|
|
|
, [Open, Operation "bar", Or, Operation "baz", Close, And, Not, Operation "foo"]
|
|
|
|
)
|
|
|
|
,
|
|
|
|
( [Operation "foo", Operation "bar"]
|
|
|
|
, [Operation "foo", And, Operation "bar"]
|
|
|
|
)
|
|
|
|
,
|
|
|
|
( [Operation "foo", Not, Operation "bar"]
|
|
|
|
, [Operation "foo", And, Not, Operation "bar"]
|
|
|
|
)
|
|
|
|
]
|