{-# LANGUAGE UnicodeSyntax, FlexibleContexts #-} -- | Patterns allow monadic scrutinisation of the graph (modifications are not possible) while keeping track of matched nodes (history). A 'Pattern' is interpreted by 'runPattern' that returns a result for each position in the graph where the pattern matches. It is allowed to 'fail' inside the 'Pattern' monad, indicating that the pattern does not match, which corresponds to conditional rewriting. module GraphRewriting.Pattern (module GraphRewriting.Pattern, PatternT, Pattern, Match, (<|>)) where import Prelude.Unicode import GraphRewriting.Pattern.Internal import GraphRewriting.Graph.Read import Control.Monad.Reader import Control.Monad.List import Control.Monad.Identity import qualified Data.Set as Set (empty, insert, member) import Control.Applicative import Data.Functor import Data.Monoid -- | A pattern represents a graph scrutinisation that memorises all the scrutinised nodes during matching. type Pattern n = PatternT n Identity instance Monad m ⇒ Monad (PatternT n m) where return x = PatternT $ \h → return ([],x) p >>= f = PatternT $ \h → do (m1,x) ← patternT p h (m2,y) ← patternT (f x) (reverse m1 ⧺ h) return (m1 ⧺ m2, y) fail str = PatternT $ \h → lift (fail str) instance MonadTrans (PatternT n) where lift m = PatternT $ \h → do x ← lift $ lift m return ([],x) -- TODO: Change constraint to Functor m if possible instance Monad m ⇒ Functor (PatternT n m) where fmap = liftM -- TODO: Change constraint from Monad m if possible instance Monad m ⇒ Applicative (PatternT n m) where pure = return f <*> x = do f' ← f f' <$> x instance Monad m ⇒ Alternative (PatternT n m) where empty = mzero (<|>) = mplus instance Monad m ⇒ Monoid (PatternT n m a) where mempty = mzero mappend = mplus instance Monad m ⇒ MonadPlus (PatternT n m) where mzero = fail "empty result list" mplus p q = PatternT $ \h → do -- TODO: this implements choice. Is mplus the right function for that? g ← ask lift $ runReaderT (patternT p h) g `mplus` runReaderT (patternT q h) g runPatternT ∷ PatternT n m a → Graph n → m [(Match,a)] runPatternT = runPatternT' [] -- | Apply a pattern on a graph returning a result for each matching position in the graph together with the matched nodes. runPattern ∷ Pattern n a → Graph n → [(Match,a)] runPattern p = runIdentity . runPatternT p evalPattern ∷ Pattern n a → Graph n → [a] evalPattern p = map snd . runPattern p execPattern ∷ Pattern n a → Graph n → [Match] execPattern p = map fst . runPattern p -- combinators --------------------------------------------------------------- -- | Something like an implicit monadic map branch ∷ Monad m ⇒ [a] → PatternT n m a -- TODO: express this using Alternative? branch xs = PatternT $ \h → lift $ ListT $ return [([],x) | x ← xs] -- | 'branch' on each node, add it to the history, and return it branchNodes ∷ Monad m ⇒ [Node] → PatternT n m Node branchNodes ns = do -- TODO: express this using Alternative? n ← branch ns visit n return n -- | Probe whether a pattern matches somewhere on the graph. You might want to combine this with 'amnesia'. probe ∷ Monad m ⇒ PatternT n m a → PatternT n m Bool probe p = not . null <$> matches p -- | probe a pattern returning the matches it has on the graph. You might want to combine this with 'amnesia'. matches ∷ Monad m ⇒ PatternT n m a → PatternT n m [Match] matches p = map fst <$> match p -- TODO: isn't this essentially same as runPatternT? -- | probe a pattern returning the matches it has on the graph. You might want to combine this with 'amnesia'. match ∷ Monad m ⇒ PatternT n m a → PatternT n m [(Match, a)] match p = PatternT $ \h → do matches ← liftM (runReaderT $ patternT p h) ask -- list of all possible matches let roundup = liftM (\xs → [(concatMap fst xs, xs)]) (runListT matches) -- concatenation into one big match lift $ ListT roundup -- | choice over a list of patterns anyOf ∷ Alternative f ⇒ [f a] → f a anyOf = foldr (<|>) empty -- | conditional rewriting: 'fail' when predicate is not met require ∷ Monad m ⇒ Bool → m () require p = unless p $ fail "requirement not met" -- | 'fail' if given pattern succeeds, succeed if it fails. requireFailure ∷ Monad m ⇒ PatternT n m a → PatternT n m () requireFailure p = require . not =<< probe p -- | 'fail' when monadic predicate is not met requireM ∷ Monad m ⇒ m Bool → m () requireM p = p >>= require -- some base patterns -------------------------------------------------------- -- | Lift a scrutinisation from 'Reader' to 'Pattern' leaving the history unchanged. liftReader ∷ Monad m ⇒ Reader (Graph n) a → PatternT n m a liftReader r = PatternT $ \h → do x ← runReader r `liftM` ask return ([],x) -- | any node anywhere in the graph node ∷ (Monad m, View v n) ⇒ PatternT n m v node = liftReader . inspectNode =<< branchNodes =<< liftReader readNodeList -- | A specific node nodeAt ∷ (Monad m, View v n) ⇒ Node → PatternT n m v nodeAt ref = do n ← liftReader $ inspectNode ref PatternT $ \h → lift $ return ([ref],n) -- | any edge anywhere in the graph edge ∷ Monad m ⇒ PatternT n m Edge edge = branch =<< liftReader readEdgeList -- | node that is connected to given edge nodeWith ∷ (Monad m, View v n) ⇒ Edge → PatternT n m v nodeWith e = liftReader . inspectNode =<< branchNodes =<< liftReader (attachedNodes e) -- | edge that is attached to given node edgeOf ∷ (Monad m, View [Port] n) ⇒ Node → PatternT n m Edge edgeOf n = branch =<< liftReader (attachedEdges n) -- | node that is connected to the given node, but not that node itself neighbour ∷ Monad m => (View [Port] n, View v n) ⇒ Node → PatternT n m v neighbour n = liftReader . inspectNode =<< branchNodes =<< liftReader (neighbours n) -- | node that is connected to the given node, permitting the node itself relative ∷ (Monad m, View [Port] n, View v n) ⇒ Node → PatternT n m v relative n = liftReader . inspectNode =<< branchNodes =<< liftReader (relatives n) -- | nodes connected to given port of the specified node, not including the node itself. -- Consider as an alternative 'linear' combined with 'nodeWith'. adverse ∷ (Monad m, View [Port] n, View v n) ⇒ Port → Node → PatternT n m v adverse p n = liftReader . inspectNode =<< branchNodes =<< liftReader (adverseNodes n p) -- controlling history and future -------------------------------------------- -- | A specific node visit ∷ Monad m ⇒ Node → PatternT n m () visit n = do exists ← liftReader $ existNode n if exists then PatternT $ \h → lift $ return ([n],()) else fail $ "visit: node with ID " ⧺ show n ⧺ " does not exist" -- | Do not remember any of the nodes matched by the supplied pattern amnesia ∷ Monad m ⇒ PatternT n m a → PatternT n m a amnesia p = PatternT $ \h → do (h',x) ← patternT p h return ([],x) -- | list of nodes matched until now with the most recent node in head position history ∷ Monad m ⇒ PatternT n m Match history = PatternT $ \h → return ([],h) -- | a reference to the lastly matched node previous ∷ Monad m ⇒ PatternT n m Node previous = head <$> history -- | only match nodes in the next pattern that have not been matched before nextFresh ∷ Monad m ⇒ PatternT n m a → PatternT n m a nextFresh = restrictOverlap $ \past future → null future ∨ not (head future ∈ past) -- | only accept the given node in the next match nextIs ∷ Monad m ⇒ Node → PatternT n m a → PatternT n m a nextIs next = restrictOverlap $ \past future → not (null future) ∧ head future ≡ next -- | Restrict a pattern based on the which of nodes have matched been previously and which nodes will be matched in the future. The first parameter of the supplied function is the history with the most recently matched node in head position. The second parameter is the future with the next matched node in head position. restrictOverlap ∷ Monad m ⇒ (Match → Match → Bool) → PatternT n m a → PatternT n m a restrictOverlap c p = PatternT $ \h → do (h',x) ← patternT p h require (c h h') return (h',x) -- TODO: the check is only done after the whole pattern has matched (maybe do the check more often inbetween?) -- | Nodes in the future may not be matched more than once. linear ∷ Monad m ⇒ PatternT n m a → PatternT n m a linear = restrictOverlap $ \hist future → isLinear Set.empty future where isLinear left [] = True isLinear left (r:rs) = not (r `Set.member` left) ∧ isLinear (r `Set.insert` left) rs