c-expr-dsl-0.1.0.0: src/C/Expr/Util/Parsec.hs
module C.Expr.Util.Parsec (
-- * Character streams
caseInsensitive'
, foldCharTokens
, satisfyWith
-- * General purpose
, Consumer(..)
, foldTokens
) where
import Control.Monad (guard)
import Data.Char (toLower)
import Text.Parsec (Consumed (..), ParseError, ParsecT, Reply (..), SourcePos,
State (..), Stream (..), mkPT, tokenPrim, unknownError,
(<?>))
import Text.Parsec.Error (Message (..), newErrorMessage)
import Text.Parsec.Pos (updatePosChar, updatePosString)
{-------------------------------------------------------------------------------
Character streams
-------------------------------------------------------------------------------}
-- | Case-insensitive version of 'Text.Parsec.string''
--
-- Returns the parsed string (which may be different from the argument string).
caseInsensitive' :: Stream s m Char => String -> ParsecT s u m String
caseInsensitive' = \expected ->
foldCharTokens (go [] expected) <?> expected
where
go :: String -> String -> Consumer Char String
go acc [] = Done (reverse acc)
go acc (x:xs) = Look { onEof = Nothing
, onToken = \y -> do
guard (toLower x == toLower y)
return $ go (y:acc) xs
}
-- | Specialization of 'foldTokens' to streams of characters
foldCharTokens :: Stream s m Char => Consumer Char a -> ParsecT s u m a
foldCharTokens = foldTokens show updatePosString
-- | Generalization of 'Text.Parsec.satisfy' that returns evidence
satisfyWith :: Stream s m Char => (Char -> Maybe a) -> ParsecT s u m a
satisfyWith =
tokenPrim show updatePos
where
updatePos :: SourcePos -> Char -> s -> SourcePos
updatePos pos c _ = updatePosChar pos c
{-------------------------------------------------------------------------------
General purpose
-------------------------------------------------------------------------------}
data Consumer t a =
Done a
| Look {
onEof :: Maybe a
, onToken :: t -> Maybe (Consumer t a)
}
-- | Fold a sequence of tokens; no input is consumed on failure.
--
-- This is a generalization of 'Text.Parsec.tokens'', which can be defined in terms of
-- 'foldTokens' as follows:
--
-- > tokens' showTokens updatePos expected =
-- > foldTokens showTokens updatePos (go expected) <?> showTokens expected
-- > where
-- > go :: [t] -> Consumer t [t]
-- > go [] = Done expected
-- > go (t:ts) = Look { onEof = Nothing
-- > , onToken = \t' -> guard (t == t') >> return (go ts)
-- > }
foldTokens :: forall s u m t a.
Stream s m t
=> ([t] -> String)
-> (SourcePos -> [t] -> SourcePos)
-> Consumer t a
-> ParsecT s u m a
foldTokens showTokens updatePos = \f ->
mkPT $ \st -> aux st f
where
aux :: State s u -> Consumer t a -> m (Consumed (m (Reply s u a)))
aux initState =
walk [] (stateInput initState)
where
walk :: [t] -> s -> Consumer t a -> m (Consumed (m (Reply s u a)))
walk acc rs (Done a) = ok acc rs a
walk acc rs Look{onEof, onToken} = do
mNextToken <- uncons rs
case mNextToken of
Nothing -> case onEof of
Nothing -> err errEof
Just a -> ok acc rs a
Just (t, rs') -> case onToken t of
Nothing -> err (errUnexpected t)
Just k -> walk (t:acc) rs' k
ok :: [t] -> s -> a -> m (Consumed (m (Reply s u a)))
ok acc rs a = return $
(if null acc then Empty else Consumed) $
return $ Ok a finalState (unknownError finalState)
where
finalState :: State s u
finalState = State{
statePos = updatePos (statePos initState) (reverse acc)
, stateUser = stateUser initState
, stateInput = rs
}
err :: ParseError -> m (Consumed (m (Reply s u a)))
err e = return $ Empty $ return $ Error $ e
errEof :: ParseError
errEof =
newErrorMessage (SysUnExpect "") (statePos initState)
errUnexpected :: t -> ParseError
errUnexpected t =
newErrorMessage (SysUnExpect $ showTokens [t]) (statePos initState)