packages feed

regex-generator-0.1.0.0: src/Text/Regex/Regen/Pattern.hs

{-# LANGUAGE RecordWildCards #-}
module Text.Regex.Regen.Pattern where

import Control.Monad.Catch (MonadThrow)
import Data.Map (Map)
import Data.Monoid ((<>))
import qualified Data.ByteString.Char8 as S
import qualified Data.Map as Map

import Text.Regex.Regen.PatternException

data Pattern = Pattern
    { pGroups     :: !(Map Int Group)
    , pGroupNames :: !(Map S.ByteString Int)
    , pGroupCount :: !Int
    , pOptions    :: !Options
    } deriving (Show, Eq)

emptyPattern :: Options -> Pattern
emptyPattern = Pattern Map.empty Map.empty 0

lookupGroupByNum :: Int -> Pattern -> Maybe Group
lookupGroupByNum n Pattern {..} = Map.lookup n pGroups

lookupGroupByName :: S.ByteString -> Pattern -> Maybe Group
lookupGroupByName bs Pattern {..} = do
    n <- Map.lookup bs pGroupNames
    Map.lookup n pGroups

data Part
    = Empty
    | Byte Char
    | Sequence [Part]
    | CClass Bool [Char]
    | Quantified Part Range Strategy
    | Alternative [Part]
    | Reference Int
    | Call (Either Int S.ByteString)
    | Anchored Part [Anchor] Part
    deriving (Show, Eq)
instance Monoid Part where
    mempty = Empty
    Empty        `mappend` p            = p
    p            `mappend` Empty        = p
    Sequence ps1 `mappend` Sequence ps2 = nonEmptySeq $ ps1 <> ps2
    Sequence ps  `mappend` p            = nonEmptySeq $ reverse (p : reverse ps)
    p            `mappend` Sequence ps  = nonEmptySeq $ p : ps
    p1           `mappend` p2           = Sequence [p1,p2]

data Anchor
    = WordBoundary
    | WordInternal
    | Start
    | End
    | EndBeforeNewline
    | BeforeNewline
    | AfterNewline
    | StartOfMatch
    deriving (Show, Eq)

data Group = Group
    { gIndex :: !Int
    , gParts :: !Part
    } deriving (Show, Eq)

data Range = Range
    { rMin :: !Int
    , rMax :: !(Maybe Int)
    } deriving (Show, Eq)

oLineEndChars :: Options -> [Char]
oLineEndChars o = case oLineEnds o of
    CR      -> ['\r']
    LF      -> ['\n']
    CRLF    -> []
    AnyCRLF -> ['\r','\n']
    Any     -> ['\r','\n']

nonEmptyAltM :: MonadThrow m => [Part] -> m Part
nonEmptyAltM [] = eUnsatisfiable
nonEmptyAltM ps = pure $ Alternative ps

nonEmptyCClassM :: MonadThrow m => (Char -> Bool) -> [Char] -> m Part
nonEmptyCClassM p cs = case filter p cs of
    []  -> eUnsatisfiable
    cs' -> pure $ CClass True cs'

nonEmptyQuantM :: MonadThrow m => Part -> Range -> Strategy -> m Part
nonEmptyQuantM p (Range 0 (Just 0)) s = eUnsatisfiable
nonEmptyQuantM p (Range 0 m)        s = pure $ Quantified p (Range 1 m) s
nonEmptyQuantM p r                  s = pure $ Quantified p r           s

nonEmptyAlt :: [Part] -> Part
nonEmptyAlt [] = Empty
nonEmptyAlt ps = Alternative ps

nonEmptySeq :: [Part] -> Part
nonEmptySeq [] = Empty
nonEmptySeq ps = Sequence ps

data Options = Options
    { oDefaultStrategy :: !Strategy
    , oMultiline       :: !Bool
    , oIgnoreCase      :: !Bool
    , oLineEnds        :: !LineEnds
    , oRecursion       :: !Bool
    , oDotAll          :: !Bool
    , oFreeSpacing     :: !Bool
    , oDupNames        :: !Bool
    } deriving (Show, Eq)

defaultOptions :: Options
defaultOptions = Options
    { oDefaultStrategy = Greedy
    , oMultiline       = False
    , oIgnoreCase      = False
    , oLineEnds        = AnyCRLF
    , oRecursion       = False
    , oDotAll          = False
    , oFreeSpacing     = False
    , oDupNames        = False
    }

data LineEnds
    = CR      -- \r
    | LF      -- \n
    | CRLF    -- \r\n
    | AnyCRLF -- (\r|\n|\r\n)
    | Any     -- Any unicode newline sequence
    deriving (Show, Eq)

data Strategy
    = Greedy
    | Lazy
    | Possessive
    deriving (Show, Eq)