t-regex-0.1.0.0: src/Data/Regex/Rules.hs
{-# LANGUAGE KindSignatures #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE ConstraintKinds #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE FunctionalDependencies #-}
{-# LANGUAGE TypeSynonymInstances #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE RankNTypes #-}
-- | Attribute grammars with regular expression matching.
module Data.Regex.Rules (
-- * Basic blocks
Action, Rule, Grammar,
eval,
-- * Nice syntax for defining rules
rule,
-- ** Combinators
check,
(->>>), (->>),
-- ** Special lenses
this, at,
inh, syn
) where
import Control.Applicative
import Control.Monad.State
import Data.Foldable (foldMap)
import Data.Map (Map)
import qualified Data.Map as M
import Data.Maybe (fromJust)
import Data.Monoid
import Data.Regex.Generics
-- | Actions create new inherited attributes for their children,
-- and synthesized attribute for its own node, from the synthesized
-- attributes of children and the inheritance from its parent.
-- Additionally, actions may include an explicit backtrack.
type Action c (f :: * -> *) inh syn = Fix f -> inh -> Map c syn -> (Bool, Map c inh, syn)
-- | A rule comprises the regular expression to match
-- and the action to execute if successful.
type Rule c (f :: * -> *) inh syn = (Regex c f, Action c f inh syn)
-- | A grammar is simply a list of rules.
type Grammar c (f :: * -> *) inh syn = [Rule c f inh syn]
-- | Evaluate an attribute grammar over a certain term.
eval :: (Ord c, Matchable f, Monoid syn)
=> Grammar c f inh syn -> inh -> Fix f -> syn
eval grammar down term = fromJust $ foldr (<|>) empty $ map evalRule grammar
where evalRule (regex, action) = do -- Maybe monad
(captures :: Map c [Fix f]) <- match regex term
let (ok, children, up) = action term down $ M.mapWithKey evalList captures
evalList k = foldMap $ eval grammar (children M.! k)
guard ok
return up
data ActionState c inh syn = ActionState { _apply :: Bool, _this :: (inh, syn), _rest :: Map c (inh, syn) }
-- | Lens for the attributes of the current node. To be used in composition with 'inh' or 'syn'.
this :: Functor f => ((inh,syn) -> f (inh,syn))
-> ActionState c inh syn -> f (ActionState c inh syn)
this go (ActionState ok th rs) = (\x -> ActionState ok x rs) <$> go th
{-# INLINE this #-}
-- | Lens the attributes of a child node. To be used in composition with 'inh' or 'syn'.
at :: (Ord c, Functor f) => c -> ((inh,syn) -> f (inh,syn))
-> ActionState c inh syn -> f (ActionState c inh syn)
at k go (ActionState ok th rs) = (\x -> ActionState ok th (M.insert k x rs)) <$> go (rs M.! k)
{-# INLINE at #-}
-- | Lens for the inherited attributes of a node.
-- Use only as getter with 'this' and as setter with 'at'.
inh :: Functor f => (inh -> f inh) -> (inh, syn) -> f (inh, syn)
inh go (i,s) = (\x -> (x,s)) <$> go i
{-# INLINE inh #-}
-- | Lens the inherited synthesized attributes of a node.
-- Use only as setter with 'this' and as getter with 'at'.
syn :: Functor f => (syn -> f syn) -> (inh, syn) -> f (inh, syn)
syn go (i,s) = (\x -> (i,x)) <$> go s
{-# INLINE syn #-}
stateToAction :: (Ord c, Monoid syn)
=> [c] -> (Fix f -> State (ActionState c inh syn) ())
-> Action c f inh syn
stateToAction nodes st term down up =
let initialRest = M.fromList $ map (\c -> (c, (down, up M.! c))) nodes -- down copy rule
initial = ActionState True (down, mempty) initialRest -- start with empty
ActionState ok th rs = execState (st term) initial
in (ok, M.map fst rs, snd th)
-- | Separates matching and attribute calculation on a rule.
-- The action should take as extra parameter the node which was matched.
(->>>) :: Monoid syn
=> (forall k. Regex' k Integer f) -> (Fix f -> State (ActionState Integer inh syn) ())
-> [Integer] -> Rule Integer f inh syn
(rx ->>> st) nodes = (Regex rx, stateToAction nodes st)
-- | Separates matching and attribute calculation on a rule.
(->>) :: Monoid syn
=> (forall k. Regex' k Integer f) -> State (ActionState Integer inh syn) ()
-> [Integer] -> Rule Integer f inh syn
rx ->> st = rx ->>> const st
-- | Makes the attribute calculation fail if the condition is false.
-- This function can be used to add extra conditions over whether
-- a certain rule should be applied (a bit like guards).
check :: Bool -> State (ActionState Integer inh syn) ()
check ok = modify (\(ActionState _ th rs) -> ActionState ok th rs)
class RuleBuilder (f :: * -> *) inh syn fn r | fn -> r, r -> f inh syn where
-- | Converts a rule description into an actual 'Rule'.
-- Its use must follow this pattern:
--
-- * A block of lambda-bound variables will introduce the capture names,
-- * A tree regular expression to match should capture using the previous names,
-- * After '->>>' or '->>', the state calculation should proceed.
--
-- > rule $ \c1 c2 ->
-- > regex ... c1 <<- ... c2 <<- ... ->> do
-- > at c2 . inh .= ... -- Set inherited for children
-- > c1Syn <- use (at c1 . syn) -- Get synthesized from children
-- > this . syn .= ... -- Set upwards synthesized attributes
rule :: fn -> r
instance Monoid syn =>
RuleBuilder f inh syn
([Integer] -> Rule Integer f inh syn)
(Rule Integer f inh syn) where
rule r = r []
instance Monoid syn =>
RuleBuilder f inh syn
(Integer -> [Integer] -> Rule Integer f inh syn)
(Rule Integer f inh syn) where
rule r = r 1 [1]
instance Monoid syn =>
RuleBuilder f inh syn
(Integer -> Integer -> [Integer] -> Rule Integer f inh syn)
(Rule Integer f inh syn) where
rule r = r 1 2 [1,2]
instance Monoid syn =>
RuleBuilder f inh syn
(Integer -> Integer -> Integer -> [Integer] -> Rule Integer f inh syn)
(Rule Integer f inh syn) where
rule r = r 1 2 3 [1,2,3]
instance Monoid syn =>
RuleBuilder f inh syn
(Integer -> Integer -> Integer -> Integer -> [Integer] -> Rule Integer f inh syn)
(Rule Integer f inh syn) where
rule r = r 1 2 3 4 [1,2,3,4]
instance Monoid syn =>
RuleBuilder f inh syn
(Integer -> Integer -> Integer -> Integer -> Integer -> [Integer] -> Rule Integer f inh syn)
(Rule Integer f inh syn) where
rule r = r 1 2 3 4 5 [1,2,3,4,5]