packages feed

ideas-0.6: src/Domain/RegularExpr/Expr.hs

{-# OPTIONS -XTypeSynonymInstances #-}
-----------------------------------------------------------------------------
-- Copyright 2010, 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)
--
-----------------------------------------------------------------------------
module Domain.RegularExpr.Expr where

import Common.Rewriting
import Common.Traversable
import Common.Uniplate
import Control.Monad
import Domain.Math.Expr.Symbolic
import Test.QuickCheck

--------------------------------------------------------------------
-- Data type declaration

infixl 4 :|:
infixl 5 :*:

type RegExp = RE String

data RE a = EmptySet | Epsilon | Atom a | Option (RE a) | Star (RE a)
          | Plus (RE a) | RE a :*: RE a | RE a :|: RE a
   deriving (Show, Eq, Ord)

--------------------------------------------------------------------
-- Fold

foldRE (es, eps, at, opt, st, pl, sq, ch) = rec 
 where
   rec regexp = 
      case regexp of
         EmptySet -> es
         Epsilon  -> eps
         Atom a   -> at a
         Option r -> opt (rec r)
         Star r   -> st (rec r)
         Plus r   -> pl (rec r)
         r :*: s  -> sq (rec r) (rec s)
         r :|: s  -> ch (rec r) (rec s)

--------------------------------------------------------------------
-- General instances

instance Functor RE where
   fmap f = foldRE (EmptySet, Epsilon, Atom . f, Option, Star, Plus, (:*:), (:|:))

instance Crush RE where
   crush (Atom a) = [a]
   crush regexp   = concatMap crush (children regexp)

instance Arbitrary RegExp where
   arbitrary = sized (arbRE $ oneof $ map return ["a", "b", "c", "d"])
instance CoArbitrary RegExp where
   coarbitrary = foldRE 
      (         variant 0
      ,         variant 1
      , \a ->   variant 2 . coarbitrary a
      , \a ->   variant 3 . a
      , \a ->   variant 4 . a
      , \a ->   variant 5 . a
      , \a b -> variant 6 . a . b
      , \a b -> variant 7 . a . b
      )

arbRE :: Gen a -> Int -> Gen (RE a)
arbRE g n 
   | n == 0 = frequency 
        [ (6, liftM Atom g)
        , (3, return Epsilon)
        , (1, return EmptySet)
        ]
   | otherwise = frequency 
        [ (3, arbRE g 0)
        , (2, unop Star) -- (1, unop Option), (1, unop Plus)
        , (3, binop (:*:)), (3, binop (:|:))
        ]
 where
   rec     = arbRE g (n `div` 2)
   unop  f = liftM  f rec
   binop f = liftM2 f rec rec

--------------------------------------------------------------------
-- Pretty-printer

ppRegExp :: RegExp -> String
ppRegExp = ppWith (const id)

ppWith :: (Int -> a -> String) -> RE a -> String
ppWith f = ($ 0) . foldRE 
   (const "F", const "T", flip f, unop "?", unop "*", unop "+", binop 5 "", binop 4 "|")
 where 
   unop s a _ = parIf False (a 6 ++ s)
   binop i s a b n = parIf (n > i) (a i ++ s ++ b i)
   parIf b s = if b then "(" ++ s ++ ")" else s

--testje = ppWith (const id) (Star (Plus (Atom "P")) :*: (Option (Atom "Q" :*: Option (Atom "S")) :|: Atom "R"))

--------------------------------------------------------------------
-- Function for associative operators

concatOp :: Operator (RE a)
concatOp = associativeOperator (:*:) isConcat
 where
   isConcat (r :*: s) = Just (r, s)
   isConcat _         = Nothing

choiceOp :: Operator (RE a)
choiceOp = associativeOperator (:|:) isChoice
 where
   isChoice (r :|: s) = Just (r, s)
   isChoice _         = Nothing

--------------------------------------------------------------------
-- Instances for rewriting

instance Uniplate (RE a) where
   uniplate regexp = 
      case regexp of
         EmptySet -> ([],     \[] -> EmptySet)
         Epsilon  -> ([],     \[] -> Epsilon)
         Atom a   -> ([],     \[] -> Atom a)
         Option r -> ([r],    \[a] -> Option a)
         Star r   -> ([r],    \[a] -> Star a)
         Plus r   -> ([r],    \[a] -> Plus a)
         r :*: s  -> ([r, s], \[a, b] -> a :*: b)
         r :|: s  -> ([r, s], \[a, b] -> a :|: b)

instance Eq a => ShallowEq (RE a) where
   shallowEq re1 re2 = 
      case (re1, re2) of
         (EmptySet, EmptySet) -> True
         (Epsilon,  Epsilon ) -> True
         (Atom a,   Atom b  ) -> a==b
         (Option _, Option _) -> True
         (Star _,   Star _  ) -> True
         (Plus _,   Plus _  ) -> True
         (_ :*: _,  _ :*: _ ) -> True
         (_ :|: _,  _ :|: _ ) -> True
         _                    -> False

instance Different (RE a) where
   different = (EmptySet, Epsilon)

instance IsTerm RegExp where 
   toTerm = foldRE 
      ( nullary "EmptySet", nullary "Epsilon", variable, unary "Option"
      , unary "Star", unary "Plus", binary ":*:", binary ":|:"
      ) 

   fromTerm a = fromTermWith f a `mplus` liftM Atom (getVariable a)
    where
      f s []     
         | s == "EmptySet" = return EmptySet
         | s == "Epsilon"  = return Epsilon
      f s [x]    
         | s == "Option"   = return (Option x)
         | s == "Star"     = return (Star x)
         | s == "Plus"     = return (Plus x)
      f s [x, y] 
         | s == ":*:"      = return (x :*: y)
         | s == ":|:"      = return (x :|: y)
      f _ _ = fail "fromExpr"

instance Rewrite RegExp where
   operators = [concatOp, choiceOp]
   associativeOps = const $ map toSymbol [":*:", ":|:"]