conditional-restriction-parser-0.1.0.4: src/ConditionalRestriction/Internal/Parse/ParserLib.hs
{-# LANGUAGE LambdaCase #-}
-- | Parsing library. Implements a simple 'Parser' type, and some basic parsers, e.g. 'ws', 'dbl'.
module ConditionalRestriction.Internal.Parse.ParserLib where
import ConditionalRestriction.Result (Result (..))
import Control.Applicative (Alternative (empty, many, (<|>)))
import Control.Monad (replicateM, (>=>))
import Data.Bifunctor (Bifunctor (first))
-- | A generic parser. Takes an input type @i@ and returns an output type @a@.
newtype Parser i a = Parser
{ parse :: i -> Result String (a, i)
}
instance Functor (Parser i) where
fmap f (Parser p) = Parser $ fmap (first f) . p
instance Applicative (Parser i) where
pure x = Parser $ \i -> Ok (x, i)
(Parser pf) <*> (Parser px) = Parser $ pf >=> \(f, i') -> first f <$> px i'
instance Alternative (Parser i) where
empty = Parser $ \i -> Err "No parsing possibilities left."
(Parser a) <|> (Parser b) = Parser $ \i -> case a i of
res@(Ok _) -> res
Err msg -> b i
instance Monad (Parser i) where
return = pure
(Parser pa) >>= f = Parser $ \i -> case pa i of
Ok (a, i') -> parse (f a) i'
Err msg -> Err msg
str :: String -> Parser String String
str s = Parser $ \i ->
if take len i == s
then Ok (s, drop len i)
else Err $ "Input does not match '" ++ s ++ "': " ++ shorten 16 i
where
len = length s
anyOf :: [Char] -> Parser String Char
anyOf cs = Parser $ \case
[] -> Err "Empty input."
(i : is) ->
if i `elem` cs
then Ok (i, is)
else Err $ show i ++ " does not match any of '" ++ cs ++ "'"
noneOf :: [Char] -> Parser String Char
noneOf cs = Parser $ \case
[] -> Err "Empty input."
(i : is) ->
if i `elem` cs
then Err $ show i ++ " matches '" ++ cs ++ "'"
else Ok (i, is)
ws :: Parser String String
ws = many (anyOf "\t\n ")
word :: String -> Parser String String
word s = str s <* ws
tok :: Parser String String
tok = many (noneOf "\t\n ") <* ws
dbl :: Parser String Double
dbl = Parser $ \i -> case reads i of
[(x, rem)] -> Ok (x, rem)
_ -> Err $ "No double: " ++ shorten 16 i
bint :: Int -> Parser String Int
bint max =
read <$> case digits max of
[] -> str "0"
(x : xs) ->
(:) <$> anyOf ['0' .. d2c (x - 1)] <*> replicateM (length xs) (anyOf ['0' .. '9'])
<|> (:) <$> anyOf [d2c x] <*> mapM (\x' -> anyOf ['0' .. d2c x']) xs
where
digits 0 = []
digits x = digits (x `div` 10) ++ [x `mod` 10]
d2c = head . show
end :: Parser String ()
end = Parser $ \case
[] -> Ok ((), "")
i -> Err $ "There is still input left: " ++ i
shorten :: Int -> String -> String
shorten len str
| len > 3 =
if length str > len
then take (len - 3) str ++ "..."
else str
shorten len str = take len str
strip :: String -> String
strip = drop_ws . reverse . drop_ws . reverse
where
drop_ws = dropWhile (`elem` "\t\n ")