{-# LANGUAGE CPP #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE MultiWayIf #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE Rank2Types #-} {-# LANGUAGE RecordWildCards #-} {-# LANGUAGE RecursiveDo #-} {-# LANGUAGE TypeFamilies #-} {-# OPTIONS -fno-warn-orphans #-} module Text.MegaparsecSpec (spec) where import Control.Monad.Cont import Control.Monad.Except import Control.Monad.Identity import Control.Monad.Reader import Data.Char (toUpper, isLetter) import Data.Foldable (asum, concat) import Data.Function (on) import Data.List (isPrefixOf) import Data.List.NonEmpty (NonEmpty (..)) import Data.Maybe (fromMaybe, listToMaybe, isJust) import Data.Monoid import Data.Proxy import Data.String import Data.Void import Prelude hiding (span, concat) import Test.Hspec import Test.Hspec.Megaparsec import Test.Hspec.Megaparsec.AdHoc import Test.QuickCheck hiding (label) import Text.Megaparsec import Text.Megaparsec.Char import qualified Control.Monad.RWS.Lazy as L import qualified Control.Monad.RWS.Strict as S import qualified Control.Monad.State.Lazy as L import qualified Control.Monad.State.Strict as S import qualified Control.Monad.Writer.Lazy as L import qualified Control.Monad.Writer.Strict as S import qualified Data.List as DL import qualified Data.List.NonEmpty as NE import qualified Data.Semigroup as G import qualified Data.Set as E import qualified Data.Text as T import qualified Data.ByteString as BS #if !MIN_VERSION_base(4,8,0) import Control.Applicative hiding (many, some) #endif #if !MIN_VERSION_QuickCheck(2,8,2) instance (Arbitrary a, Ord a) => Arbitrary (E.Set a) where arbitrary = E.fromList <$> arbitrary shrink = fmap E.fromList . shrink . E.toList #endif spec :: Spec spec = do describe "position in custom stream" $ do describe "eof" $ it "updates position in stream correctly" $ property $ \st -> (not . null . stateInput) st ==> do let p = eof :: CustomParser () h = head (stateInput st) apos = let (_:|z) = statePos st in spanStart h :| z runParser' p st `shouldBe` ( st { statePos = apos } , Left (err apos $ utok h <> eeof) ) describe "token" $ do context "when input stream is empty" $ it "signals correct parse error" $ property $ \st'@State {..} span -> do let p = pSpan span st = (st' :: State [Span]) { stateInput = [] } runParser' p st `shouldBe` ( st , Left (err statePos $ ueof <> etok span) ) context "when head of stream matches" $ it "updates parser state correctly" $ property $ \st'@State {..} span -> do let p = pSpan span st = st' { stateInput = span : stateInput } npos = spanEnd span :| NE.tail statePos runParser' p st `shouldBe` ( st { statePos = npos , stateTokensProcessed = stateTokensProcessed + 1 , stateInput = stateInput } , Right span ) context "when head of stream does not match" $ do let checkIt s span = let ms = listToMaybe s in isJust ms && (spanBody <$> ms) /= Just (spanBody span) it "signals correct parse error" $ property $ \st@State {..} span -> checkIt stateInput span ==> do let p = pSpan span h = head stateInput apos = spanStart h :| NE.tail statePos runParser' p st `shouldBe` ( st { statePos = apos } , Left (err apos $ utok h <> etok span)) describe "tokens" $ it "updates position in stream correctly" $ property $ \st' ts -> forAll (incCoincidence st' ts) $ \st@State {..} -> do let p = tokens compareTokens ts :: CustomParser [Span] compareTokens = (==) `on` fmap spanBody compareToken = (==) `on` spanBody il = length . takeWhile id $ zipWith compareToken stateInput ts tl = length ts consumed = take il stateInput (apos, npos) = let (pos:|z) = statePos pxy = Proxy :: Proxy [Span] in ( positionAt1 pxy pos (head stateInput) :| z , advanceN pxy stateTabWidth pos consumed :| z ) if | null ts -> runParser' p st `shouldBe` (st, Right []) | null stateInput -> runParser' p st `shouldBe` ( st , Left (err statePos $ ueof <> etoks ts) ) | il == tl -> runParser' p st `shouldBe` ( st { statePos = npos , stateTokensProcessed = stateTokensProcessed + fromIntegral tl , stateInput = drop tl stateInput } , Right consumed ) | otherwise -> runParser' p st `shouldBe` ( st { statePos = apos } , Left (err apos $ utoks (take tl stateInput) <> etoks ts) ) describe "takeWhileP" $ it "updates position in stream correctly" $ property $ \st@State {..} -> do let p = takeWhileP Nothing (const True) :: CustomParser [Span] st' = st { stateInput = [] , statePos = case stateInput of [] -> statePos xs -> let _:|z = statePos in spanEnd (last xs) :| z , stateTokensProcessed = stateTokensProcessed + length stateInput } runParser' p st `shouldBe` (st', Right stateInput) describe "takeWhile1P" $ do context "when stream is prefixed with matching tokens" $ it "updates position in stream correctly" $ property $ \st@State {..} -> not (null stateInput) ==> do let p = takeWhile1P Nothing (const True) :: CustomParser [Span] st' = st { stateInput = [] , statePos = case stateInput of [] -> statePos xs -> let _:|z = statePos in spanEnd (last xs) :| z , stateTokensProcessed = stateTokensProcessed + length stateInput } runParser' p st `shouldBe` (st', Right stateInput) context "when stream is not prefixed with at least one matching token" $ it "updates position in stream correctly" $ property $ \st@State {..} -> do let p = takeWhile1P Nothing (const False) :: CustomParser [Span] fst (runParser' p st) `shouldBe` st describe "takeP" $ do context "when stream has enough tokens" $ it "updates position in stream correctly" $ property $ \st@State {..} -> not (null stateInput) ==> do let p = takeP Nothing (length stateInput) :: CustomParser [Span] st' = st { stateInput = [] , statePos = case stateInput of [] -> statePos xs -> let _:|z = statePos in spanEnd (last xs) :| z , stateTokensProcessed = stateTokensProcessed + length stateInput } runParser' p st `shouldBe` (st', Right stateInput) context "when stream has not enough tokens" $ it "updates position in stream correctly" $ property $ \st@State {..} -> not (null stateInput) ==> do let p = takeP Nothing (1 + length stateInput) :: CustomParser [Span] (pos:|z) = statePos st' = st { statePos = positionAtN (Proxy :: Proxy [Span]) pos stateInput :| z } fst (runParser' p st) `shouldBe` st' describe "getNextTokenPosition" $ do context "when input stream is empty" $ it "returns Nothing" $ property $ \st' -> do let p :: CustomParser (Maybe SourcePos) p = getNextTokenPosition st = (st' :: State [Span]) { stateInput = [] } runParser' p st `shouldBe` (st, Right Nothing) context "when input stream is not empty" $ it "return the position of start of the next token" $ property $ \st' h -> do let p :: CustomParser (Maybe SourcePos) p = getNextTokenPosition st = st' { stateInput = h : stateInput st' } runParser' p st `shouldBe` (st, (Right . Just . spanStart) h) describe "ParsecT Semigroup instance" $ it "the associative operation works" $ property $ \a b -> do let p = pure [a] G.<> pure [b] prs p "" `shouldParse` ([a,b] :: [Int]) describe "ParsecT Monoid instance" $ do it "mempty works" $ do let p = mempty prs p "" `shouldParse` ([] :: [Int]) it "mappend works" $ property $ \a b -> do let p = pure [a] `mappend` pure [b] prs p "" `shouldParse` ([a,b] :: [Int]) describe "ParsecT IsString instance" $ do describe "equivalence to 'string'" $ do it "for String" $ property $ \s i -> eqParser (string s) (fromString s) (i :: String) it "for Text" $ property $ \s i -> eqParser (string (T.pack s)) (fromString s) (i :: T.Text) it "for ByteString" $ property $ \s i -> eqParser (string (fromString s :: BS.ByteString)) (fromString s) (i :: BS.ByteString) it "can handle Unicode" $ do let r = "פּאַרסער 解析器" :: BS.ByteString p :: Parsec Void BS.ByteString BS.ByteString p = BS.concat <$> sequence ["פּאַ", "רסער", " 解析器"] parse p "" r `shouldParse` r describe "ParsecT Functor instance" $ do it "obeys identity law" $ property $ \n -> prs (fmap id (pure (n :: Int))) "" === prs (id (pure n)) "" it "obeys composition law" $ property $ \n m t -> let f = (+ m) g = (* t) in prs (fmap (f . g) (pure (n :: Int))) "" === prs ((fmap f . fmap g) (pure n)) "" describe "ParsecT Applicative instance" $ do it "obeys identity law" $ property $ \n -> prs (pure id <*> pure (n :: Int)) "" === prs (pure n) "" it "obeys composition law" $ property $ \n m t -> let u = pure (+ m) v = pure (* t) w = pure (n :: Int) in prs (pure (.) <*> u <*> v <*> w) "" === prs (u <*> (v <*> w)) "" it "obeys homomorphism law" $ property $ \x m -> let f = (+ m) in prs (pure f <*> pure (x :: Int)) "" === prs (pure (f x)) "" it "obeys interchange law" $ property $ \n y -> let u = pure (+ n) in prs (u <*> pure (y :: Int)) "" === prs (pure ($ y) <*> u) "" describe "(<*>)" $ context "when first parser succeeds without consuming" $ context "when second parser fails consuming input" $ it "fails consuming input" $ do let p = m <*> n m = return (\x -> 'a' : x) n = string "bc" <* empty s = "bc" prs p s `shouldFailWith` err (posN (4 :: Int) s) mempty prs' p s `failsLeaving` "" describe "(*>)" $ it "works correctly" $ property $ \n m -> let u = pure (+ (m :: Int)) v = pure (n :: Int) in prs (u *> v) "" === prs (pure (const id) <*> u <*> v) "" describe "(<*)" $ it "works correctly" $ property $ \n m -> let u = pure (m :: Int) v = pure (+ (n :: Int)) in prs (u <* v) "" === prs (pure const <*> u <*> v) "" describe "ParsecT Alternative instance" $ do describe "empty" $ it "always fails" $ property $ \n -> prs (empty <|> pure n) "" `shouldParse` (n :: Integer) describe "(<|>)" $ do context "with two strings" $ do context "stream begins with the first string" $ it "parses the string" $ property $ \s0 s1 s -> not (s1 `isPrefixOf` s0) ==> do let s' = s0 ++ s p = string s0 <|> string s1 prs p s' `shouldParse` s0 prs' p s' `succeedsLeaving` s context "stream begins with the second string" $ it "parses the string" $ property $ \s0 s1 s -> not (s0 `isPrefixOf` s1) && not (s0 `isPrefixOf` s) ==> do let s' = s1 ++ s p = string s0 <|> string s1 prs p s' `shouldParse` s1 prs' p s' `succeedsLeaving` s context "when stream does not begin with either string" $ it "signals correct error message" $ property $ \s0 s1 s -> not (s0 `isPrefixOf` s) && not (s1 `isPrefixOf` s) ==> do let p = string s0 <|> string s1 z = take (max (length s0) (length s1)) s prs p s `shouldFailWith` err posI (etoks s0 <> etoks s1 <> (if null s then ueof else utoks z)) context "with two complex parsers" $ do context "when stream begins with matching character" $ it "parses it" $ property $ \a b -> a /= b ==> do let p = char a <|> (char b *> char a) s = [a] prs p s `shouldParse` a prs' p s `succeedsLeaving` "" context "when stream begins with only one matching character" $ it "signals correct parse error" $ property $ \a b c -> a /= b && a /= c ==> do let p = char a <|> (char b *> char a) s = [b,c] prs p s `shouldFailWith` err (posN (1 :: Int) s) (utok c <> etok a) prs' p s `failsLeaving` [c] context "when stream begins with not matching character" $ it "signals correct parse error" $ property $ \a b c -> a /= b && a /= c && b /= c ==> do let p = char a <|> (char b *> char a) s = [c,b] prs p s `shouldFailWith` err posI (utok c <> etok a <> etok b) prs' p s `failsLeaving` s context "when stream is emtpy" $ it "signals correct parse error" $ property $ \a b -> do let p = char a <|> (char b *> char a) prs p "" `shouldFailWith` err posI (ueof <> etok a <> etok b) it "associativity of fold over alternatives should not matter" $ do let p = asum [empty, string ">>>", empty, return "foo"] "bar" p' = bsum [empty, string ">>>", empty, return "foo"] "bar" bsum = foldl (<|>) empty s = ">>" prs p s `shouldBe` prs p' s describe "many" $ do context "when stream begins with things argument of many parses" $ it "they are parsed" $ property $ \a' b' c' -> do let [a,b,c] = getNonNegative <$> [a',b',c'] p = many (char 'a') s = abcRow a b c prs p s `shouldParse` replicate a 'a' prs' p s `succeedsLeaving` drop a s context "when stream does not begin with thing argument of many parses" $ it "does nothing" $ property $ \a' b' c' -> do let [a,b,c] = getNonNegative <$> [a',b',c'] p = many (char 'd') s = abcRow a b c prs p s `shouldParse` "" prs' p s `succeedsLeaving` s context "when stream is empty" $ it "succeeds parsing nothing" $ do let p = many (char 'a') prs p "" `shouldParse` "" context "when there are two many combinators in a row that parse nothing" $ it "accumulated hints are reflected in parse error" $ do let p = many (char 'a') *> many (char 'b') *> eof prs p "c" `shouldFailWith` err posI (utok 'c' <> etok 'a' <> etok 'b' <> eeof) context "when the argument parser succeeds without consuming" $ it "is run nevertheless" $ property $ \n' -> do let n = getSmall (getNonNegative n') :: Integer p = void . many $ do x <- S.get if x < n then S.modify (+ 1) else empty v :: S.State Integer (Either (ParseError Char Void) ()) v = runParserT p "" ("" :: String) S.execState v 0 `shouldBe` n describe "some" $ do context "when stream begins with things argument of some parses" $ it "they are parsed" $ property $ \a' b' c' -> do let a = getPositive a' [b,c] = getNonNegative <$> [b',c'] p = some (char 'a') s = abcRow a b c prs p s `shouldParse` replicate a 'a' prs' p s `succeedsLeaving` drop a s context "when stream does not begin with thing argument of some parses" $ it "signals correct parse error" $ property $ \a' b' c' -> do let [a,b,c] = getNonNegative <$> [a',b',c'] p = some (char 'd') s = abcRow a b c ++ "g" prs p s `shouldFailWith` err posI (utok (head s) <> etok 'd') prs' p s `failsLeaving` s context "when stream is empty" $ it "signals correct parse error" $ property $ \ch -> do let p = some (char ch) prs p "" `shouldFailWith` err posI (ueof <> etok ch) context "optional" $ do context "when stream begins with that optional thing" $ it "parses it" $ property $ \a b -> do let p = optional (char a) <* char b s = [a,b] prs p s `shouldParse` Just a prs' p s `succeedsLeaving` "" context "when stream does not begin with that optional thing" $ it "succeeds parsing nothing" $ property $ \a b -> a /= b ==> do let p = optional (char a) <* char b s = [b] prs p s `shouldParse` Nothing prs' p s `succeedsLeaving` "" context "when stream is empty" $ it "succeeds parsing nothing" $ property $ \a -> do let p = optional (char a) prs p "" `shouldParse` Nothing describe "ParsecT Monad instance" $ do it "satisfies left identity law" $ property $ \a k' -> do let k = return . (+ k') p = return (a :: Int) >>= k prs p "" `shouldBe` prs (k a) "" it "satisfies right identity law" $ property $ \a -> do let m = return (a :: Int) p = m >>= return prs p "" `shouldBe` prs m "" it "satisfies associativity law" $ property $ \m' k' h' -> do let m = return (m' :: Int) k = return . (+ k') h = return . (* h') p = m >>= (\x -> k x >>= h) p' = (m >>= k) >>= h prs p "" `shouldBe` prs p' "" it "fails signals correct parse error" $ property $ \msg -> do let p = fail msg :: Parsec Void String () prs p "" `shouldFailWith` errFancy posI (fancy $ ErrorFail msg) it "pure is the same as return" $ property $ \n -> prs (pure (n :: Int)) "" `shouldBe` prs (return n) "" it "(<*>) is the same as ap" $ property $ \m' k' -> do let m = return (m' :: Int) k = return (+ k') prs (k <*> m) "" `shouldBe` prs (k `ap` m) "" describe "ParsecT MonadFail instance" $ describe "fail" $ it "signals correct parse error" $ property $ \s msg -> do let p = void (fail msg) prs p s `shouldFailWith` errFancy posI (fancy $ ErrorFail msg) prs' p s `failsLeaving` s describe "ParsecT MonadIO instance" $ it "liftIO works" $ property $ \n -> do let p = liftIO (return n) :: ParsecT Void String IO Integer runParserT p "" "" `shouldReturn` Right n describe "ParsecT MonadFix instance" $ it "withRange works" $ do let withRange :: (MonadParsec e s m, MonadFix m) => ((SourcePos,SourcePos) -> m a) -> m a withRange f = do Just p1 <- getNextTokenPosition rec r <- f (p1, p2) p2 <- getPosition return r p :: Parsec Void String (SourcePos,SourcePos) p = withRange $ \pp -> pp <$ string "ab" runParser p "" "abcd" `shouldBe` Right ( SourcePos "" (mkPos 1) (mkPos 1) , SourcePos "" (mkPos 1) (mkPos 3) ) describe "ParsecT MonadReader instance" $ do describe "ask" $ it "returns correct value of context" $ property $ \n -> do let p = ask :: ParsecT Void String (Reader Integer) Integer runReader (runParserT p "" "") n `shouldBe` Right n describe "local" $ it "modifies reader context correctly" $ property $ \n k -> do let p = local (+ k) ask :: ParsecT Void String (Reader Integer) Integer runReader (runParserT p "" "") n `shouldBe` Right (n + k) describe "ParsecT MonadState instance" $ do describe "get" $ it "returns correct state value" $ property $ \n -> do let p = L.get :: ParsecT Void String (L.State Integer) Integer L.evalState (runParserT p "" "") n `shouldBe` Right n describe "put" $ it "replaces state value" $ property $ \a b -> do let p = L.put b :: ParsecT Void String (L.State Integer) () L.execState (runParserT p "" "") a `shouldBe` b describe "ParsecT MonadCont instance" $ describe "callCC" $ it "works properly" $ property $ \a b -> do let p :: ParsecT Void String (Cont (Either (ParseError Char Void) Integer)) Integer p = callCC $ \e -> when (a > b) (e a) >> return b runCont (runParserT p "" "") id `shouldBe` Right (max a b) describe "ParsecT MonadError instance" $ do describe "throwError" $ it "throws the error" $ property $ \a b -> do let p :: ParsecT Void String (Except Integer) Integer p = throwError a >> return b runExcept (runParserT p "" "") `shouldBe` Left a describe "catchError" $ it "catches the error" $ property $ \a b -> do let p :: ParsecT Void String (Except Integer) Integer p = (throwError a >> return b) `catchError` handler handler e = return (e + b) runExcept (runParserT p "" "") `shouldBe` Right (Right $ a + b) describe "primitive combinators" $ do describe "failure" $ it "signals correct parse error" $ property $ \us ps -> do let p :: MonadParsec Void String m => m () p = void (failure us ps) grs p "" (`shouldFailWith` TrivialError posI us ps) describe "fancyFailure" $ it "singals correct parse error" $ property $ \xs -> do let p :: MonadParsec Void String m => m () p = void (fancyFailure xs) grs p "" (`shouldFailWith` FancyError posI xs) describe "label" $ do context "when inner parser succeeds consuming input" $ do context "inner parser does not produce any hints" $ it "collection of hints remains empty" $ property $ \lbl a -> not (null lbl) ==> do let p :: MonadParsec Void String m => m Char p = label lbl (char a) <* empty s = [a] grs p s (`shouldFailWith` err (posN (1 :: Int) s) mempty) grs' p s (`failsLeaving` "") context "inner parser produces hints" $ it "replaces the last hint with “the rest of