ideas-0.5.8: src/Common/Strategy.hs
-----------------------------------------------------------------------------
-- Copyright 2009, Open Universiteit Nederland. This file is distributed
-- under the terms of the GNU General Public License. For more information,
-- see the file "LICENSE.txt", which is included in the distribution.
-----------------------------------------------------------------------------
-- |
-- Maintainer : bastiaan.heeren@ou.nl
-- Stability : provisional
-- Portability : portable (depends on ghc)
--
-- A strategy is a context-free grammar with rules as symbols. Strategies can be
-- labeled with strings. A type class is introduced to lift all the combinators
-- that work on strategies, only to prevent that you have to insert these lifting
-- functions yourself.
--
-----------------------------------------------------------------------------
module Common.Strategy
( -- * Data types and type classes
Strategy, LabeledStrategy, strategyName, unlabel
, IsStrategy(..)
-- * Running strategies
, fullDerivationTree, derivationTree
-- * Strategy combinators
-- ** Basic combinators
, (<*>), (<|>), (<||>), succeed, fail, label, sequence, alternatives
-- ** EBNF combinators
, many, many1, replicate, option
-- ** Negation and greedy combinators
, check, not, repeat, repeat1, try, (|>), exhaustive
-- ** Traversal combinators
, fix, once, somewhere, topDown, bottomUp
-- * Strategy locations
, StrategyLocation, StrategyOrRule, strategyLocations, subStrategy
, mapRules, rulesInStrategy, cleanUpStrategy
-- * Prefixes
, Prefix, emptyPrefix, makePrefix, prefixTree, Step(..)
, prefixToSteps, stepsToRules, lastStepInPrefix
) where
import Common.Apply
import Common.Context
import Common.Derivation
import Common.Rewriting hiding (inverse)
import Common.Transformation
import Common.Uniplate (Uniplate, children)
import Common.Utils
import Prelude hiding (fail, not, repeat, replicate, sequence)
import qualified Common.Grammar as RE
import qualified Prelude as Prelude
-----------------------------------------------------------
-- Data types and type classes
-- | Abstract data type for a strategy
newtype Strategy a = S { unS :: RE.Grammar (Either (Rule a) (LabeledStrategy a)) }
-- | A strategy which is labeled with a string
data LabeledStrategy a = LS
{ strategyName :: String -- ^ Returns the label of the strategy
, unlabel :: Strategy a -- ^ Removes the label from a strategy
}
-- | Type class to turn values into strategies
class Apply f => IsStrategy f where
toStrategy :: f a -> Strategy a
-- instances for Show
instance Show a => Show (Strategy a) where
show = show . unS
instance Show a => Show (LabeledStrategy a) where
show s =
strategyName s ++ ": " ++ show (unlabel s)
-- instances for Apply
instance Apply Strategy where
applyAll s = results . fullDerivationTree s
instance Apply LabeledStrategy where
applyAll = applyAll . unlabel
-- instances for IsStrategy
instance IsStrategy RewriteRule where
toStrategy r =
toStrategy (makeRule (ruleName r) (RewriteRule r))
instance IsStrategy Rule where
toStrategy = S . RE.symbol . Left
instance IsStrategy Strategy where
toStrategy = id
instance IsStrategy (LabeledStrategy) where
toStrategy = S . RE.symbol . Right
-- instances for Lift
instance Lift Strategy where
lift lp (S re) = S (fmap (either (Left . lift lp) (Right . lift lp)) re)
instance Lift (LabeledStrategy) where
lift lp (LS n s) = LS n (lift lp s)
-----------------------------------------------------------
--- Running strategies
-- | Returns the derivation tree for a strategy and a term, including all
-- minor rules
fullDerivationTree :: IsStrategy f => f a -> a -> DerivationTree (Rule a) a
fullDerivationTree = rec . noLabels . toStrategy
where
rec s a = addBranches (list s a) (singleNode a (RE.empty s))
list s a = [ (f, rec rest b)
| (f, rest) <- RE.firsts s
, b <- applyAll f a
]
-- | Returns the derivation tree for a strategy and a term with only major rules
derivationTree :: IsStrategy f => f a -> a -> DerivationTree (Rule a) a
derivationTree s = mergeSteps isMajorRule . fullDerivationTree s
noLabels :: Strategy a -> RE.Grammar (Rule a)
noLabels = RE.join . fmap (either RE.symbol (noLabels . unlabel)) . unS
-----------------------------------------------------------
--- Strategy combinators
-- Basic combinators --------------------------------------
infixr 3 <|>
infixr 4 <||>
infixr 5 <*>
-- | Put two strategies in sequence (first do this, then do that)
(<*>) :: (IsStrategy f, IsStrategy g) => f a -> g a -> Strategy a
s <*> t = S (unS (toStrategy s) RE.<*> unS (toStrategy t))
-- | Choose between the two strategies (either do this or do that)
(<|>) :: (IsStrategy f, IsStrategy g) => f a -> g a -> Strategy a
s <|> t = S (unS (toStrategy s) RE.<|> unS (toStrategy t))
-- | Run two strategies in parallel (with interleaving)
(<||>) :: (IsStrategy f, IsStrategy g) => f a -> g a -> Strategy a
s <||> t = S (unS (toStrategy s) RE.<||> unS (toStrategy t))
-- | The strategy that always succeeds (without doing anything)
succeed :: Strategy a
succeed = S RE.succeed
-- | The strategy that always fails
fail :: Strategy a
fail = S RE.fail
-- | Labels a strategy with a string
label :: IsStrategy f => String -> f a -> LabeledStrategy a
label l = LS l . toStrategy
-- | Puts a list of strategies into a sequence
sequence :: IsStrategy f => [f a] -> Strategy a
sequence = foldr ((<*>) . toStrategy) succeed
-- | Combines a list of alternative strategies
alternatives :: IsStrategy f => [f a] -> Strategy a
alternatives = foldr ((<|>) . toStrategy) fail
-- EBNF combinators --------------------------------------
-- | Repeat a strategy zero or more times (non-greedy)
many :: IsStrategy f => f a -> Strategy a
many = S . RE.many . unS . toStrategy
-- | Apply a certain strategy at least once (non-greedy)
many1 :: IsStrategy f => f a -> Strategy a
many1 s = s <*> many s
-- | Apply a strategy a certain number of times
replicate :: IsStrategy f => Int -> f a -> Strategy a
replicate n = sequence . Prelude.replicate n
-- | Apply a certain strategy or do nothing (non-greedy)
option :: IsStrategy f => f a -> Strategy a
option s = s <|> succeed
-- Negation and greedy combinators ----------------------
infixr 4 |>
-- | Checks whether a predicate holds for the current term. The
-- check is considered to be a minor step.
check :: (a -> Bool) -> Strategy a
check p = toStrategy checkRule
where
checkRule = minorRule $ makeSimpleRule "check" $ \a ->
if p a then Just a else Nothing
-- | Check whether or not the argument strategy cannot be applied: the result
-- strategy only succeeds if this is not the case (otherwise it fails).
not :: IsStrategy f => f a -> Strategy a
not s = check (Prelude.not . applicable (toStrategy s))
{- alternative definition, with an early commit. No performance gain was
measurable
applicableOne :: Strategy a -> a -> Bool
applicableOne s a =
let tree = derivationTree s a
in endpoint tree || Prelude.not (null (branches tree)) -}
-- | Repeat a strategy zero or more times (greedy version of 'many')
repeat :: IsStrategy f => f a -> Strategy a
repeat s = many s <*> not s
-- | Apply a certain strategy at least once (greedy version of 'many1')
repeat1 :: IsStrategy f => f a -> Strategy a
repeat1 s = many1 s <*> not s
-- | Apply a certain strategy if this is possible (greedy version of 'option')
try :: IsStrategy f => f a -> Strategy a
try s = s <|> not s
-- | Left-biased choice: if the left-operand strategy can be applied, do so. Otherwise,
-- try the right-operand strategy
(|>) :: (IsStrategy f, IsStrategy g) => f a -> g a -> Strategy a
s |> t = s <|> (not s <*> t)
-- | Apply the strategies from the list exhaustively (until this is no longer possible)
exhaustive :: IsStrategy f => [f a] -> Strategy a
exhaustive = repeat . alternatives
-- Traversal combinators --------------------------------------------
-- | A fix-point combinator on strategies (to model recursion). Powerful
-- (but dangerous) combinator
fix :: (Strategy a -> Strategy a) -> Strategy a
fix f = S $ RE.fix $ unS . f . S
-- | Apply a strategy on (exactly) one of the term's direct children
once :: (IsStrategy f, Uniplate a) => f (Context a) -> Strategy (Context a)
once s = ruleMoveDown <*> s <*> ruleMoveUp
where
ruleMoveDown = minorRule $ makeSimpleRuleList "MoveDown" moveDown
moveDown c =
let n = maybe 0 (pred . length . children) (currentFocus c)
in [ changeLocation (locationDown i) c | i <- [0 .. n] ]
ruleMoveUp = minorRule $ makeSimpleRule "MoveUp" moveUp
moveUp c = do
new <- locationUp (location c)
return $ setLocation new c
-- | Apply a strategy somewhere in the term
somewhere :: (IsStrategy f, Uniplate a) => f (Context a) -> Strategy (Context a)
somewhere s = fix $ \this -> s <|> once this
-- | Search for a suitable location in the term to apply the strategy using a
-- top-down approach
topDown :: (IsStrategy f, Uniplate a) => f (Context a) -> Strategy (Context a)
topDown s = fix $ \this -> s |> once this
-- | Search for a suitable location in the term to apply the strategy using a
-- bottom-up approach
bottomUp :: (IsStrategy f, Uniplate a) => f (Context a) -> Strategy (Context a)
bottomUp s = fix $ \this -> once this <|> (not (once (bottomUp s)) <*> s)
{- The ideal implementation does not yet work: there appears to be a strange
interplay between the fixpoint operator (with variables) and the not combinator
> bottomUp s = fix $ \this -> once this |> s -}
-----------------------------------------------------------
--- Strategy locations
-- | A strategy location corresponds to a substrategy or a rule
type StrategyLocation = [Int]
type StrategyOrRule a = Either (LabeledStrategy a) (Rule a)
-- | Returns a list of all strategy locations, paired with the labeled
-- substrategy or rule at that location
strategyLocations :: LabeledStrategy a -> [(StrategyLocation, Either (LabeledStrategy a) (Rule a))]
strategyLocations = rec []
where
rec loc ns =
let f is = either (\r -> [ (is, Right r) | isMajorRule r ]) (rec is)
xs = RE.collectSymbols $ combine (,) loc $ unS $ unlabel ns
in (loc, Left ns) : concatMap (uncurry f) xs
-- | Returns the substrategy or rule at a strategy location. Nothing indicates that the location is invalid
subStrategy :: StrategyLocation -> LabeledStrategy a -> Maybe (StrategyOrRule a)
subStrategy loc s =
case loc of
[] -> return (Left s)
n:ns ->
case lookup n . RE.collectSymbols . RE.withIndex . unS . unlabel $ s of
Just (Left r) | null ns -> return (Right r)
Just (Right t) -> subStrategy ns t
_ -> Nothing
-- | Apply a function to all the rules that make up a labeled strategy
mapRules :: (Rule a -> Rule b) -> LabeledStrategy a -> LabeledStrategy b
mapRules fun = f
where
f (LS n s) = LS n (g s)
g (S expr) = S (fmap h expr)
h (Left r) = Left (fun r)
h (Right ls) = Right (f ls)
-- | Returns a list of all major rules that are part of a labeled strategy
rulesInStrategy :: LabeledStrategy a -> [Rule a]
rulesInStrategy s = [ r | (_, Right r) <- strategyLocations s ]
-- local helper-function
combine :: ([Int] -> a -> b) -> [Int] -> RE.Grammar a -> RE.Grammar b
combine g is = fmap (\(i, a) -> g (is++[i]) a) . RE.withIndex
-- | Use a function as do-after hook for all rules in a labeled strategy
cleanUpStrategy :: (a -> a) -> LabeledStrategy a -> LabeledStrategy a
cleanUpStrategy f s = mapRules g (label (strategyName s) (doAfter f idRule <*> unlabel s))
where
g r | isMajorRule r = doAfter f r
| otherwise = r
-----------------------------------------------------------
--- Prefixes
-- | Abstract data type for a (labeled) strategy with a prefix (a sequence of
-- executed rules). A prefix is still "aware" of the labels that appear in the
-- strategy. A prefix is encoded as a list of integers (and can be reconstructed
-- from such a list: see @makePrefix@). The list is stored in reversed order.
data Prefix a = P [(Int, Step a)] (RE.Grammar (Step a))
instance Show (Prefix a) where
show (P xs _) = show (reverse (map fst xs))
instance Eq (Prefix a) where
P xs _ == P ys _ = map fst xs == map fst ys
-- | Construct the empty prefix for a labeled strategy
emptyPrefix :: LabeledStrategy a -> Prefix a
emptyPrefix = makePrefix []
-- | Construct a prefix for a given list of integers and a labeled strategy.
makePrefix :: [Int] -> LabeledStrategy a -> Prefix a
makePrefix is ls = rec [] is start
where
start = withSteps ls
rec acc [] g = P acc g
rec acc (n:ns) g =
case drop n (RE.firsts g) of
(z, h):_ -> rec ((n, z):acc) ns h
_ -> P [] start
-- | The @Step@ data type can be used to inspect the structure of the strategy
data Step a = Begin StrategyLocation
| Step StrategyLocation (Rule a)
| End StrategyLocation
deriving (Show, Eq)
instance Apply Step where
applyAll (Step _ r) = applyAll r
applyAll (Begin _) = return
applyAll (End _) = return
instance Apply Prefix where
applyAll p = results . prefixTree p
-- | Create a derivation tree with a "prefix" as annotation.
prefixTree :: Prefix a -> a -> DerivationTree (Prefix a) a
prefixTree (P xs g) a =
addBranches list (singleNode a (RE.empty g))
where
add (i, (step, rest)) = P ((i, step):xs) rest
list = [ (newPrefix, prefixTree newPrefix b)
| triple@(_, (step, _)) <- zip [0..] (RE.firsts g)
, let newPrefix = add triple
, b <- applyAll step a
]
-- | Returns the steps that belong to the prefix
prefixToSteps :: Prefix a -> [Step a]
prefixToSteps (P xs _) = map snd (reverse xs)
-- | Retrieves the rules from a list of steps
stepsToRules :: [Step a] -> [Rule a]
stepsToRules steps = [ r | Step _ r <- steps ]
-- | Returns the last rule of a prefix (if such a rule exists)
lastStepInPrefix :: Prefix a -> Maybe (Step a)
lastStepInPrefix (P xs _) = safeHead (map snd xs)
-- local helper function
withSteps :: LabeledStrategy a -> RE.Grammar (Step a)
withSteps = rec []
where
rec is = mark is . RE.join . combine f is . unS . unlabel
f is = either (RE.symbol . Step is) (rec is)
mark is g =
let begin = RE.symbol (Begin is)
end = RE.symbol (End is)
in begin RE.<*> g RE.<*> end