module Text.Regex.TDFA.Pattern
(Pattern(..)
,PatternSet(..)
,PatternSetCharacterClass(..)
,PatternSetCollatingElement(..)
,PatternSetEquivalenceClass(..)
,GroupIndex
,DoPa(..)
,showPattern
,starTrans
,starTrans',simplify',dfsPattern
) where
import Data.List(intersperse,partition)
import qualified Data.Set as Set(toAscList,toList)
import Data.Set(Set)
import Text.Regex.TDFA.Common(DoPa(..),GroupIndex,common_error)
err :: String -> a
err = common_error "Text.Regex.TDFA.Pattern"
data Pattern = PEmpty
| PGroup (Maybe GroupIndex) Pattern
| POr [Pattern]
| PConcat [Pattern]
| PQuest Pattern
| PPlus Pattern
| PStar Bool Pattern
| PBound Int (Maybe Int) Pattern
| PCarat {getDoPa::DoPa}
| PDollar {getDoPa::DoPa}
| PDot {getDoPa::DoPa}
| PAny {getDoPa::DoPa,getPatternSet::PatternSet}
| PAnyNot {getDoPa::DoPa,getPatternSet::PatternSet}
| PEscape {getDoPa::DoPa,getPatternChar::Char}
| PChar {getDoPa::DoPa,getPatternChar::Char}
| PNonCapture Pattern
| PNonEmpty Pattern
deriving (Eq,Show)
showPattern :: Pattern -> String
showPattern pIn =
case pIn of
PEmpty -> "()"
PGroup _ p -> paren (showPattern p)
POr ps -> concat $ intersperse "|" (map showPattern ps)
PConcat ps -> concatMap showPattern ps
PQuest p -> (showPattern p)++"?"
PPlus p -> (showPattern p)++"+"
PStar _ p -> (showPattern p)++"*"
PBound i (Just j) p | i==j -> showPattern p ++ ('{':show i)++"}"
PBound i mj p -> showPattern p ++ ('{':show i) ++ maybe ",}" (\j -> ',':show j++"}") mj
PCarat _ -> "^"
PDollar _ -> "$"
PDot _ -> "."
PAny _ ps -> ('[':show ps)++"]"
PAnyNot _ ps -> ('[':'^':show ps)++"]"
PEscape _ c -> '\\':c:[]
PChar _ c -> [c]
PNonCapture p -> showPattern p
PNonEmpty p -> showPattern p
where
paren s = ('(':s)++")"
data PatternSet = PatternSet (Maybe (Set Char))
(Maybe (Set PatternSetCharacterClass))
(Maybe (Set PatternSetCollatingElement))
(Maybe (Set PatternSetEquivalenceClass))
deriving (Eq)
instance Show PatternSet where
showsPrec i (PatternSet s scc sce sec) =
let (special,normal) = maybe ("","") ((partition (`elem` "]-")) . Set.toAscList) s
charSpec = (if ']' `elem` special then (']':) else id) (byRange normal)
scc' = maybe "" ((concatMap show) . Set.toList) scc
sce' = maybe "" ((concatMap show) . Set.toList) sce
sec' = maybe "" ((concatMap show) . Set.toList) sec
in shows charSpec
. showsPrec i scc' . showsPrec i sce' . showsPrec i sec'
. if '-' `elem` special then showChar '-' else id
where byRange xAll@(x:xs) | length xAll <=3 = xAll
| otherwise = groupRange x 1 xs
byRange _ = undefined
groupRange x n (y:ys) = if (fromEnum y)-(fromEnum x) == n then groupRange x (succ n) ys
else (if n <=3 then take n [x..]
else x:'-':(toEnum (pred n+fromEnum x)):[]) ++ groupRange y 1 ys
groupRange x n [] = if n <=3 then take n [x..]
else x:'-':(toEnum (pred n+fromEnum x)):[]
newtype PatternSetCharacterClass = PatternSetCharacterClass {unSCC::String}
deriving (Eq,Ord)
newtype PatternSetCollatingElement = PatternSetCollatingElement {unSCE::String}
deriving (Eq,Ord)
newtype PatternSetEquivalenceClass = PatternSetEquivalenceClass {unSEC::String}
deriving (Eq,Ord)
instance Show PatternSetCharacterClass where
showsPrec _ p = showChar '[' . showChar ':' . shows (unSCC p) . showChar ':' . showChar ']'
instance Show PatternSetCollatingElement where
showsPrec _ p = showChar '[' . showChar '.' . shows (unSCE p) . showChar '.' . showChar ']'
instance Show PatternSetEquivalenceClass where
showsPrec _ p = showChar '[' . showChar '=' . shows (unSEC p) . showChar '=' . showChar ']'
starTrans :: Pattern -> Pattern
starTrans = dfsPattern (simplify' . starTrans')
dfsPattern :: (Pattern -> Pattern)
-> Pattern
-> Pattern
dfsPattern f = dfs
where unary c = f . c . dfs
dfs pattern = case pattern of
POr ps -> f (POr (map dfs ps))
PConcat ps -> f (PConcat (map dfs ps))
PGroup i p -> unary (PGroup i) p
PQuest p -> unary PQuest p
PPlus p -> unary PPlus p
PStar i p -> unary (PStar i) p
PBound i mi p -> unary (PBound i mi) p
_ -> f pattern
reGroup :: Pattern -> Pattern
reGroup p@(PConcat xs) | 2 <= length xs = PGroup Nothing p
reGroup p@(POr xs) | 2 <= length xs = PGroup Nothing p
reGroup p = p
starTrans' :: Pattern -> Pattern
starTrans' pIn =
case pIn of
PQuest p -> POr [p,PEmpty]
PPlus p | canOnlyMatchNull p -> p
| otherwise -> asGroup $ PConcat [reGroup p,PStar False p]
PBound i _ _ | i<0 -> PEmpty
PBound i (Just j) _ | i>j -> PEmpty
PBound _ (Just 0) _ -> PEmpty
PBound 0 Nothing p | canOnlyMatchNull p -> quest p
| otherwise -> PStar True p
PBound 0 (Just 1) p -> quest p
PBound i Nothing p | canOnlyMatchNull p -> p
| otherwise -> asGroup . PConcat $ apply (nc'p:) (pred i) [reGroup p,PStar False p]
where nc'p = nonCapture' p
PBound 0 (Just j) p | canOnlyMatchNull p -> quest p
| otherwise -> quest . (concat' p) $
apply (nonEmpty' . (concat' p)) (j-2) (nonEmpty' p)
PBound i (Just j) p | canOnlyMatchNull p -> p
| i == j -> asGroup . PConcat $ apply (nc'p:) (pred i) [reGroup p]
| otherwise -> asGroup . PConcat $ apply (nc'p:) (pred i)
[reGroup p,apply (nonEmpty' . (concat' p)) (j-i-1) (ne'p) ]
where nc'p = nonCapture' p
ne'p = nonEmpty' p
PStar mayFirstBeNull p | canOnlyMatchNull p -> if mayFirstBeNull then quest p
else PEmpty
| otherwise -> pass
PEmpty -> pass
PGroup {} -> pass
POr {} -> pass
PConcat {} -> pass
PCarat {} -> pass
PDollar {} -> pass
PDot {} -> pass
PAny {} -> pass
PAnyNot {} -> pass
PEscape {} -> pass
PChar {} -> pass
PNonCapture {} -> pass
PNonEmpty {} -> pass
where
quest = (\ p -> POr [p,PEmpty])
concat' a b = simplify' $ PConcat [reGroup a,reGroup b]
nonEmpty' = (\ p -> simplify' $ POr [PEmpty,p])
nonCapture' = PNonCapture
apply f n x = foldr ($) x (replicate n f)
asGroup p = PGroup Nothing (simplify' p)
pass = pIn
simplify' :: Pattern -> Pattern
simplify' x@(POr _) =
let ps' = case span notPEmpty (flatten x) of
(notEmpty,[]) -> notEmpty
(notEmpty,_:rest) -> notEmpty ++ (PEmpty:filter notPEmpty rest)
in case ps' of
[] -> PEmpty
[p] -> p
_ -> POr ps'
simplify' x@(PConcat _) =
let ps' = filter notPEmpty (flatten x)
in case ps' of
[] -> PEmpty
[p] -> p
_ -> PConcat ps'
simplify' (PStar _ PEmpty) = PEmpty
simplify' (PNonCapture PEmpty) = PEmpty
simplify' other = other
flatten :: Pattern -> [Pattern]
flatten (POr ps) = (concatMap (\x -> case x of
POr ps' -> ps'
p -> [p]) ps)
flatten (PConcat ps) = (concatMap (\x -> case x of
PConcat ps' -> ps'
p -> [p]) ps)
flatten _ = err "flatten can only be applied to POr or PConcat"
notPEmpty :: Pattern -> Bool
notPEmpty PEmpty = False
notPEmpty _ = True
canOnlyMatchNull :: Pattern -> Bool
canOnlyMatchNull pIn =
case pIn of
PEmpty -> True
PGroup _ p -> canOnlyMatchNull p
POr ps -> all canOnlyMatchNull ps
PConcat ps -> all canOnlyMatchNull ps
PQuest p -> canOnlyMatchNull p
PPlus p -> canOnlyMatchNull p
PStar _ p -> canOnlyMatchNull p
PBound _ (Just 0) _ -> True
PBound _ _ p -> canOnlyMatchNull p
PCarat _ -> True
PDollar _ -> True
PNonCapture p -> canOnlyMatchNull p
_ ->False