{-# LANGUAGE FlexibleInstances #-} -- | Home-grown parser, just because. module Haste.Parsing ( Parse, runParser, char, charP, string, oneOf, possibly, atLeast, whitespace, word, Haste.Parsing.words, int, double, positiveDouble, suchThat, quotedString, skip, rest, lookahead, anyChar ) where import Control.Applicative import Control.Monad import Data.Char newtype Parse a = Parse {unP :: (String -> Maybe (String, a))} runParser :: Parse a -> String -> Maybe a runParser (Parse p) s = case p s of Just ("", x) -> Just x _ -> Nothing instance Monad Parse where return x = Parse $ \s -> Just (s, x) Parse m >>= f = Parse $ \s -> do (s', x) <- m s unP (f x) s' instance Alternative Parse where empty = mzero (<|>) = mplus instance MonadPlus Parse where mplus (Parse p1) (Parse p2) = Parse $ \s -> case p1 s of x@(Just _) -> x _ -> p2 s mzero = Parse $ const Nothing instance Functor Parse where fmap f (Parse g) = Parse $ fmap (fmap f) . g instance Applicative Parse where pure = return (<*>) = ap -- | Read one character. Fails if end of stream. anyChar :: Parse Char anyChar = Parse $ \s -> case s of (c:cs) -> Just (cs, c) _ -> Nothing -- | Require a specific character. char :: Char -> Parse Char char c = charP (== c) -- | Parse a character that matches a given predicate. charP :: (Char -> Bool) -> Parse Char charP p = Parse $ \s -> case s of (c:next) | p c -> Just (next, c) _ -> Nothing -- | Require a specific string. string :: String -> Parse String string str = Parse $ \s -> let len = length str (s', next) = splitAt len s in if s' == str then Just (next, str) else Nothing -- | Apply the first matching parser. oneOf :: [Parse a] -> Parse a oneOf = msum -- | Invoke a parser with the possibility of failure. possibly :: Parse a -> Parse (Maybe a) possibly p = oneOf [Just <$> p, return Nothing] -- | Invoke a parser at least n times. atLeast :: Int -> Parse a -> Parse [a] atLeast 0 p = do x <- possibly p case x of Just x' -> do xs <- atLeast 0 p return (x':xs) _ -> return [] atLeast n p = do x <- p xs <- atLeast (n-1) p return (x:xs) -- | Parse zero or more characters of whitespace. whitespace :: Parse String whitespace = atLeast 0 $ charP isSpace -- | Parse a non-empty word. A word is a string of at least one non-whitespace -- character. word :: Parse String word = atLeast 1 $ charP (not . isSpace) -- | Parse several words, separated by whitespace. words :: Parse [String] words = atLeast 0 $ word <* whitespace -- | Parse an Int. int :: Parse Int int = oneOf [read <$> atLeast 1 (charP isDigit), char '-' >> (0-) . read <$> atLeast 1 (charP isDigit)] -- | Parse a floating point number. double :: Parse Double double = oneOf [positiveDouble, char '-' >> (0-) <$> positiveDouble] -- | Parse a non-negative floating point number. positiveDouble :: Parse Double positiveDouble = do first <- atLeast 1 $ charP isDigit msecond <- possibly $ char '.' *> atLeast 1 (charP isDigit) case msecond of Just second -> return $ read $ first ++ "." ++ second _ -> return $ read first -- | Fail on unwanted input. suchThat :: Parse a -> (a -> Bool) -> Parse a suchThat p f = do {x <- p ; if f x then return x else mzero} -- | A string quoted with the given quotation mark. Strings can contain escaped -- quotation marks; escape characters are stripped from the returned string. quotedString :: Char -> Parse String quotedString q = char q *> strContents q <* char q strContents :: Char -> Parse String strContents c = do s <- atLeast 0 $ charP (\x -> x /= c && x /= '\\') c' <- lookahead anyChar if c == c' then do return s else do skip 1 c'' <- anyChar s' <- strContents c return $ s ++ [c''] ++ s' -- | Read the rest of the input. rest :: Parse String rest = Parse $ \s -> Just ("", s) -- | Run a parser with the current parsing state, but don't consume any input. lookahead :: Parse a -> Parse a lookahead p = do s' <- Parse $ \s -> Just (s, s) x <- p Parse $ \_ -> Just (s', x) -- | Skip n characters from the input. skip :: Int -> Parse () skip n = Parse $ \s -> Just (drop n s, ())