regex-generator-0.1.0.0: src/Text/Regex/Regen/Gen.hs
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE OverloadedStrings #-}
module Text.Regex.Regen.Gen where
import Control.Applicative (Alternative(..))
import Control.Exception (SomeException, mapException)
import Control.Monad (MonadPlus(..), guard, (<=<), when, void)
import Control.Monad.Catch (MonadThrow(..), MonadCatch(..))
import Control.Monad.Catch (try, fromException, toException)
import Data.List ((\\), foldl')
import Data.Map (Map)
import Data.Maybe (fromMaybe, listToMaybe, maybe, catMaybes)
import Data.Monoid ((<>))
import System.Random (Random, StdGen, random, randomR, newStdGen)
import qualified Data.ByteString.Char8 as S
import qualified Data.Map as Map
import Text.Regex.Regen.Parse
import Text.Regex.Regen.Pattern
import Text.Regex.Regen.PatternException
import Text.Regex.Regen.Util
generate :: S.ByteString -> IO S.ByteString
generate = generateOpts defaultOptions
generateOpts :: Options -> S.ByteString -> IO S.ByteString
generateOpts o bs = parsePatternOpts o bs >>= generatePatternIO
generatePatternIO :: Pattern -> IO S.ByteString
generatePatternIO p = do
(e,_) <- generatePattern p <$> newStdGen
either throwM pure e
generatePattern :: Pattern -> StdGen -> (Either SomeException S.ByteString, StdGen)
generatePattern p g = let (s,e) = runGen genTop (makeGenState p g) in (e, gsGen s)
where
state = makeGenState p g
genTop = getGroup 0 >>= genGroup
data GenState = GenState
{ gsGen :: !StdGen
, gsOptions :: !Options
, gsGroups :: !(Map Int Group)
, gsGroupNames :: !(Map S.ByteString Int)
, gsResolved :: !(Map Int S.ByteString)
} deriving Show
makeGenState :: Pattern -> StdGen -> GenState
makeGenState Pattern {..} g = GenState g pOptions pGroups pGroupNames Map.empty
newGenState :: Pattern -> IO GenState
newGenState p = makeGenState p <$> newStdGen
newtype Gen a = Gen { runGen :: GenState -> (GenState, Either SomeException a) }
instance Functor Gen where
fmap f m = Gen $ \s -> let (s',e) = runGen m s in (s', fmap f e)
instance Applicative Gen where
pure a = Gen $ \s -> (s, Right a)
m <*> n = Gen $ \s ->
let (s',f) = runGen m s
(s'',a) = runGen n s'
in (s'', f <*> a)
instance Alternative Gen where
empty = Gen $ \s -> (s, mapException fromPatEx eUnsatisfiable)
where
fromPatEx :: PatternException -> SomeException
fromPatEx = toException
m <|> n = Gen $ \s -> case runGen m s of
r@(_, Right _) -> r
(_, Left _) -> runGen n s
instance Monad Gen where
m >>= f = Gen $ \s -> case runGen m s of
(s', Left e) -> (s', Left e)
(s', Right a) -> runGen (f a) s'
fail = throwM . EGenError
instance Monoid m => Monoid (Gen m) where
mempty = pure mempty
mappend g1 g2 = g1 >>= \m1 -> g2 >>= \m2 -> pure $ m1 <> m2
instance MonadThrow Gen where
throwM e = Gen $ \s -> (s, Left $ toException e)
instance MonadCatch Gen where
catch m f = Gen $ \s -> case runGen m s of
(s', Right a) -> (s', Right a)
(s, Left e) -> case fromException e of
Nothing -> (s, Left e)
Just e' -> runGen (f e') s
evalGen :: Gen a -> GenState -> Either SomeException a
evalGen = snd .: runGen
getGenState :: Gen GenState
getGenState = Gen $ \s -> (s, Right s)
putGenState :: GenState -> Gen ()
putGenState s = Gen $ const (s, Right ())
modifyGenState' :: (GenState -> GenState) -> Gen ()
modifyGenState' f = Gen $ \s -> (f s, Right ())
modifyGenState :: (GenState -> (GenState, a)) -> Gen a
modifyGenState f = Gen $ \s -> let (s',a) = f s in (s', Right a)
getOption :: (Options -> a) -> Gen a
getOption f = f . gsOptions <$> getGenState
getNamedGroup' :: S.ByteString -> Gen (Maybe Group)
getNamedGroup' bs = lookupNamedGroup <$> getGenState
where
lookupNamedGroup :: GenState -> Maybe Group
lookupNamedGroup GenState {..} =
flip Map.lookup gsGroups =<< Map.lookup bs gsGroupNames
getNamedGroup :: S.ByteString -> Gen Group
getNamedGroup bs = getNamedGroup' bs >>=
maybe (fail $ "nonexistent group name: " ++ show bs) pure
getGroup' :: Int -> Gen (Maybe Group)
getGroup' ix = Map.lookup ix . gsGroups <$> getGenState
getGroup :: Int -> Gen Group
getGroup ix = getGroup' ix >>=
maybe (fail $ "nonexistent group index: " ++ show ix) pure
getResolved' :: Int -> Gen (Maybe S.ByteString)
getResolved' ix = Map.lookup ix . gsResolved <$> getGenState
getResolved :: Int -> Gen S.ByteString
getResolved ix = getResolved' ix >>=
maybe (fail $ "group index not yet resolved: " ++ show ix) pure
resolveRef :: Int -> S.ByteString -> Gen S.ByteString
resolveRef ix bs = modifyGenState f
where
f s = (s { gsResolved = Map.insert ix bs (gsResolved s) }, bs)
getStdGen :: Gen StdGen
getStdGen = gsGen <$> getGenState
putStdGen :: StdGen -> Gen ()
putStdGen g = modifyGenState' (\s -> s { gsGen = g })
randomGen :: Random a => Gen a
randomGen = do
g <- getStdGen
let (a,g') = random g
putStdGen g'
pure a
randomRGen :: Random a => (a,a) -> Gen a
randomRGen r = do
g <- getStdGen
let (a,g') = randomR r g
putStdGen g'
pure a
randomElemGen :: [a] -> Gen a
randomElemGen l = do
g <- getStdGen
guard $ not (null l)
let (n,g') = randomR (0, length l - 1) g
putStdGen g'
pure $ n `th` l
where
n `th` l = head $ drop n l
genGroup :: Group -> Gen S.ByteString
genGroup Group {..} = genPart gParts >>= resolveRef gIndex
genPart :: Part -> Gen S.ByteString
genPart Empty = pure S.empty
genPart (Byte c) = pure $ S.singleton c
genPart (Sequence ps) = genSequence ps
genPart (CClass b cs) = genCClass b cs
genPart (Quantified p r t) = genQuantified p r t
genPart (Alternative ps) = genAlternative ps
genPart (Reference ix) = genReference ix
genPart (Call _) = eNotSupported "pattern calls"
genPart (Anchored p as q) = genAnchored p as q
genSequence :: [Part] -> Gen S.ByteString
genSequence ps = mconcat $ genPart <$> ps
genCClass :: Bool -> [Char] -> Gen S.ByteString
genCClass b pos = S.singleton <$> randomElemGen cs
where
cs = if b then pos else ['\x00'..'\xff'] \\ pos
genQuantified :: Part -> Range -> Strategy -> Gen S.ByteString
genQuantified p (Range n m) _ = do
-- TODO: Make default spread configurable? Maybe normally distributed?
r <- randomRGen (n, fromMaybe (n + 10) m)
genSequence $ replicate r p
genAlternative :: [Part] -> Gen S.ByteString
genAlternative = genPart <=< randomElemGen
genReference :: Int -> Gen S.ByteString
genReference ix = getResolved ix <|> do
g <- getGroup ix
genGroup g
genAnchored :: Part -> [Anchor] -> Part -> Gen S.ByteString
genAnchored p1 as p2 = do
as' <- mapM (tryMaybe . resolve) as
alt <- nonEmptyAltM $ catMaybes as'
genPart alt
where
resolve StartOfMatch = eGenError "start-of-match anchors unsupported"
resolve WordBoundary = endWith isW p1 <> pure p2
<|> pure p1 <> startWith isW p2
resolve WordInternal = endWith isw p1 <> startWith isw p2
resolve Start = nullify p1 <> pure p2
resolve End = pure p1 <> nullify p2
resolve BeforeNewline = getLE >>= \le -> pure p1 <> startWith le p2
resolve AfterNewline = getLE >>= \le -> endWith le p1 <> pure p2
resolve EndBeforeNewline = getLE >>= \le -> pure p1 <> comprise le p2
getLE = getOption oLineEndChars >>= \cs -> pure (`elem` cs)
isw c = c=='_' || ('A'<=c && c<='Z') || ('a'<=c && c<='z') || ('0'<=c && c<='9')
isW = not . isw
nullify :: Part -> Gen Part
nullify part = case part of
Empty -> mempty
Byte _ -> eUnsatisfiable
Sequence ps -> if null ps then mempty else eUnsatisfiable
CClass _ _ -> eUnsatisfiable
Quantified _ r _ -> if rMin r == 0 then mempty else eUnsatisfiable
Alternative ps -> foldr (<|>) empty $ nullify <$> ps
Reference ix -> do
m <- getResolved' ix
case m of
Just bs -> if S.null bs then pure () else eUnsatisfiable
Nothing -> do
g <- getGroup ix
p <- nullify $ gParts g
void . genGroup $ Group ix p
pure $ Reference ix
Call _ -> eNotSupported "pattern calls"
Anchored p1 as p2 -> Anchored <$> nullify p1 <*> pure as <*> nullify p2
comprise :: (Char -> Bool) -> Part -> Gen Part
comprise p part = case part of
Empty -> mempty
Byte c -> if p c then pure part else eUnsatisfiable
Sequence ps -> Sequence <$> mapM (comprise p) ps
CClass b cs -> nonEmptyCClassM p $ if b then cs else ['\x00'..'\xff'] \\ cs
Quantified q r t -> Quantified <$> comprise p q <*> pure r <*> pure t
Alternative ps -> nonEmptyAlt . catMaybes <$> mapM (tryMaybe . comprise p) ps
Reference ix -> do
m <- getResolved' ix
case m of
Just bs -> if S.all p bs then pure () else eUnsatisfiable
Nothing -> do
g <- getGroup ix
p <- comprise p $ gParts g
void . genGroup $ Group ix p
pure $ Reference ix
Call _ -> eNotSupported "pattern calls"
Anchored p1 as p2 -> Anchored <$> comprise p p1 <*> pure as <*> comprise p p2
endWith :: (Char -> Bool) -> Part -> Gen Part
endWith p part = case part of
Empty -> eUnsatisfiable
Byte c -> if p c then pure part else eUnsatisfiable
Sequence ps -> case reverse ps of
[] -> eUnsatisfiable
part : ps' -> nonEmptySeq . reverse .: (:) <$> endWith p part <*> pure ps'
CClass b cs -> nonEmptyCClassM p $ if b then cs else ['\x00'..'\xff'] \\ cs
Quantified q r t -> endWith p q >>= \q' -> nonEmptyQuantM q' r t
Alternative ps -> nonEmptyAlt . catMaybes <$> mapM (tryMaybe . endWith p) ps
Reference ix -> do
m <- getResolved' ix
case m of
Just bs -> if S.null bs || p (S.last bs)
then pure ()
else eUnsatisfiable
Nothing -> do
g <- getGroup ix
p <- endWith p $ gParts g
void . genGroup $ Group ix p
pure $ Reference ix
Call ix -> eNotSupported "pattern calls"
Anchored p1 as p2 -> Anchored p1 as <$> endWith p p2
startWith :: (Char -> Bool) -> Part -> Gen Part
startWith p part = case part of
Empty -> eUnsatisfiable
Byte c -> if p c then pure part else eUnsatisfiable
Sequence ps -> case ps of
[] -> eUnsatisfiable
part : ps' -> nonEmptySeq .: (:) <$> startWith p part <*> pure ps'
CClass b cs -> nonEmptyCClassM p $ if b then cs else ['\x00'..'\xff'] \\ cs
Quantified q r t -> startWith p q >>= \q' -> nonEmptyQuantM q' r t
Alternative ps -> nonEmptyAlt . catMaybes <$> mapM (tryMaybe . startWith p) ps
Reference ix -> do
m <- getResolved' ix
case m of
Just bs -> if S.null bs || p (S.head bs)
then pure ()
else eUnsatisfiable
Nothing -> do
g <- getGroup ix
p <- startWith p $ gParts g
void . genGroup $ Group ix p
pure $ Reference ix
Call ix -> eNotSupported "pattern calls"
Anchored p1 as p2 -> Anchored <$> startWith p p1 <*> pure as <*> pure p2