packages feed

regex-generator-0.1.0.3: src/Text/Regex/Regen/ParserST.hs

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

import Control.Applicative (Alternative(..))
import Control.Monad (guard, when)
import Control.Monad.Catch (MonadThrow(..))
import Data.Attoparsec.Types ()
import Data.Char (chr, ord)
import Data.Functor (($>))
import Numeric (showHex, showOct)
import qualified Control.Exception as X
import qualified Data.Attoparsec.ByteString.Char8 as P
import qualified Data.Attoparsec.Combinator as P
import qualified Data.ByteString.Char8 as S
import qualified Data.Map as Map

import Text.Regex.Regen.Pattern
import Text.Regex.Regen.PatternException

newtype ParserST a = ParserST
    { runParserST :: Pattern -> P.Parser (Pattern, a) }
instance Functor ParserST where
    fmap f p = ParserST $ \s -> do
        (s', a) <- runParserST p s
        pure (s', f a)
instance Applicative ParserST where
    pure  a = ParserST $ \s -> pure (s, a)
    f <*> m = ParserST $ \s -> do
        (s',  g) <- runParserST f s
        (s'', a) <- runParserST m s'
        pure (s'', g a)
instance Alternative ParserST where
    empty   = ParserST $ \s -> mempty
    p <|> q = ParserST $ \s -> runParserST p s <|> runParserST q s
instance Monoid (ParserST a) where
    mempty  = lift mempty
    mappend = (<|>)
instance Monad ParserST where
    m >>= f = ParserST $ \s -> do
        (s',  a) <- runParserST m s
        (s'', b) <- runParserST (f a) s'
        pure (s'', b)
instance MonadThrow ParserST where
    throwM e = ParserST $ \_ -> X.throw e

getPattern :: ParserST Pattern
getPattern = ParserST $ \s -> pure (s,s)

putPattern :: Pattern -> ParserST ()
putPattern s = ParserST $ \_ -> pure (s,())

modifyPattern :: (Pattern -> (Pattern, a)) -> ParserST a
modifyPattern f = ParserST $ \s -> pure (f s)

modifyPattern' :: (Pattern -> Pattern) -> ParserST ()
modifyPattern' f = modifyPattern $ \s -> (f s, ())

modifyOptions :: (Options -> (Options, a)) -> ParserST a
modifyOptions f = modifyPattern $ \s -> let (o,a) = f (pOptions s)
    in (s { pOptions = o }, a)

modifyOptions' :: (Options -> Options) -> ParserST ()
modifyOptions' f = modifyOptions $ \o -> (f o, ())

setIgnoreCase, setMultiline, setDupNames, setDotAll, setFreeSpacing :: Bool -> ParserST ()
setIgnoreCase  b = modifyOptions' $ \o -> o { oIgnoreCase  = b }
setMultiline   b = modifyOptions' $ \o -> o { oMultiline   = b }
setDupNames    b = modifyOptions' $ \o -> o { oDupNames    = b }
setDotAll      b = modifyOptions' $ \o -> o { oDotAll      = b }
setFreeSpacing b = modifyOptions' $ \o -> o { oFreeSpacing = b }

setStrategy :: Strategy -> ParserST ()
setStrategy s = modifyOptions' $ \o -> o { oDefaultStrategy = s }

setLineEnds :: LineEnds -> ParserST ()
setLineEnds n = modifyOptions' $ \o -> o { oLineEnds = n }

nextGroupNum :: ParserST Int
nextGroupNum = modifyPattern $
    \p -> let n = pGroupCount p + 1 in (p { pGroupCount = n }, n)

addGroup :: Int -> Part -> ParserST ()
addGroup n part = modifyPattern' $
    \p -> p { pGroups = Map.insert n (Group n part) (pGroups p) }

groupNumInUse :: Int -> ParserST Bool
groupNumInUse n = do
    Pattern {..} <- getPattern
    pure $ 0 < n && n <= pGroupCount

groupNameInUse :: S.ByteString -> ParserST Bool
groupNameInUse bs = do
    Pattern {..} <- getPattern
    pure $ bs `elem` Map.keys pGroupNames

lookupGroupIndex :: S.ByteString -> ParserST (Maybe Int)
lookupGroupIndex bs = do
    Pattern {..} <- getPattern
    pure $ Map.lookup bs pGroupNames

addNamedGroup :: S.ByteString -> Int -> Part -> ParserST ()
addNamedGroup bs n part = do
    inUse <- groupNameInUse bs
    when inUse $ eDupGroupName bs
    addGroup n part
    modifyPattern' $
        \p -> p { pGroupNames = Map.insert bs n (pGroupNames p) }

lift :: P.Parser a -> ParserST a
lift p = ParserST $ \s -> (,) s <$> p

char :: Char -> ParserST Char
char = lift . P.char

string :: S.ByteString -> ParserST S.ByteString
string = lift . P.string

satisfy :: (Char -> Bool) -> ParserST Char
satisfy = lift . P.satisfy

endOfInput :: ParserST ()
endOfInput = lift P.endOfInput

cInRange :: Char -> Char -> ParserST Char
cInRange l u = satisfy $ \c -> l <= c && c <= u

decimal :: Integral a => ParserST a
decimal = lift P.decimal

lookAhead :: ParserST a -> ParserST a
lookAhead p = ParserST $ \s -> P.lookAhead (runParserST p s)

choice :: [ParserST a] -> ParserST a
choice ps = ParserST $ \s -> P.choice (ps' s)
    where
    ps' s = (\p -> runParserST p s) <$> ps

infix 0 <?> 
(<?>) :: ParserST a -> String -> ParserST a
p <?> t = ParserST $ \s -> runParserST p s P.<?> t

peekChar :: ParserST (Maybe Char)
peekChar = lift P.peekChar

notFollowedBy :: ParserST a -> Char -> ParserST a
p `notFollowedBy` c = do
    x <- p
    m <- peekChar
    guard $ m /= Just c
    return x

peek :: ParserST a -> ParserST (Maybe a)
peek p = (Just <$> lookAhead p) <|> return Nothing

till :: ParserST a -> S.ByteString -> ParserST [a]
till p s = reverse <$> go []
    where
    go acc = do
        mb <- peek $ string s
        case mb of
            Just t | t == s -> string s $> acc
            _               -> p >>= \a -> go $ a:acc

takeWhile :: (Char -> Bool) -> ParserST S.ByteString
takeWhile p = lift $ P.takeWhile p

takeTill :: (Char -> Bool) -> ParserST S.ByteString
takeTill p = lift $ P.takeTill p

paren, bracketed, braced, angled, squoted, escaped :: ParserST a -> ParserST a
paren     p = char '('  *> p <* char ')'
braced    p = char '{'  *> p <* char '}'
bracketed p = char '['  *> p <* char ']'
angled    p = char '<'  *> p <* char '>'
squoted   p = char '\'' *> p <* char '\''
escaped   p = char '\\' *> p

digit, letter :: ParserST Char
digit  = lift P.digit
letter = lift P.letter_ascii

hexByte :: ParserST Char
hexByte = char 'x' *> (byte' <|> braced byte' <|> pure '\0')
    where
    byte'      = byte2 <|> byte1
    byte2      = decode <$> hexDigit <*> hexDigit
    byte1      = decode <$> pure 0   <*> hexDigit
    decode a b = chr $ (a * 0x10) + b
    hexDigit   = hexDigit09 <|> hexDigitaz <|> hexDigitAZ
    hexDigit09 = (\c -> ord c - ord '0' + 0x0) <$> cInRange '0' '9'
    hexDigitaz = (\c -> ord c - ord 'a' + 0xa) <$> cInRange 'a' 'z'
    hexDigitAZ = (\c -> ord c - ord 'A' + 0xA) <$> cInRange 'A' 'Z'

octalByte :: ParserST Char
octalByte = byte' <|> char 'o' *> (braced byte' <|> eMissingBrace)
    where
    octDigit      = (\c -> ord c - ord '0') <$> cInRange '0' '7'
    byte'         = byte3 <|> byte2 <|> byte1
    byte3         = decode <$> octDigit <*> octDigit <*> octDigit
    byte2         = decode <$> pure 0   <*> octDigit <*> octDigit
    byte1         = decode <$> pure 0   <*> pure 0   <*> octDigit
    decode a b c  = chr $ (a * 64) + (b * 8) + c
    eMissingBrace = eParserError "missing '{' after '\\o'"

cescape :: ParserST Char
cescape = choice
    [ '\t'   <$ char 't'
    , '\x1b' <$ char 'e'
    , '\n'   <$ char 'n'
    , '\f'   <$ char 'f'
    , '\r'   <$ char 'r'
    , '\a'   <$ char 'a'
    , '\v'   <$ char 'v'
    , '\\'   <$ char '\\'
    , char 'c' *> eNotSupported "'\\cx' escapes"
    ]

parseST :: ParserST a -> S.ByteString -> Either String (Pattern, a)
parseST = parseSTOpts defaultOptions

parseSTOpts :: Options -> ParserST a -> S.ByteString
            -> Either String (Pattern, a)
parseSTOpts o p = P.parseOnly $ runParserST p (emptyPattern o)