{-# LANGUAGE CPP #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE MultiWayIf #-} {-# LANGUAGE Rank2Types #-} {-# LANGUAGE RecordWildCards #-} {-# LANGUAGE TypeFamilies #-} {-# OPTIONS -fno-warn-orphans #-} module Text.Megaparsec.PrimSpec (spec) where import Control.Applicative import Control.Monad.Cont import Control.Monad.Except import Control.Monad.Identity import Control.Monad.Reader import Data.Char (toUpper, chr) import Data.Foldable (asum, concat) import Data.Function (on) import Data.List (isPrefixOf, foldl') import Data.List.NonEmpty (NonEmpty (..)) import Data.Maybe (fromMaybe, listToMaybe, isJust) import Data.Monoid import Data.Proxy import Data.Word (Word8) 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.Char import Text.Megaparsec.Combinator import Text.Megaparsec.Error import Text.Megaparsec.Pos import Text.Megaparsec.Prim import Text.Megaparsec.String 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.ByteString.Char8 as B import qualified Data.ByteString.Lazy.Char8 as BL 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.Text.Lazy as TL #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 "non-String instances of Stream" $ do context "lazy ByteString" $ do it "unconses correctly" $ property $ \ch' n -> do let p = many (char ch) :: Parsec Dec BL.ByteString String s = replicate (getNonNegative n) ch ch = byteToChar ch' parse p "" (BL.pack s) `shouldParse` s it "updates position like with String" $ property $ \w pos ch -> updatePos (Proxy :: Proxy BL.ByteString) w pos ch `shouldBe` updatePos (Proxy :: Proxy String) w pos ch context "strict ByteString" $ do it "unconses correctly" $ property $ \ch' n -> do let p = many (char ch) :: Parsec Dec B.ByteString String s = replicate (getNonNegative n) ch ch = byteToChar ch' parse p "" (B.pack s) `shouldParse` s it "updates position like with String" $ property $ \w pos ch -> updatePos (Proxy :: Proxy B.ByteString) w pos ch `shouldBe` updatePos (Proxy :: Proxy String) w pos ch context "lazy Text" $ do it "unconses correctly" $ property $ \ch n -> do let p = many (char ch) :: Parsec Dec TL.Text String s = replicate (getNonNegative n) ch parse p "" (TL.pack s) `shouldParse` s it "updates position like with String" $ property $ \w pos ch -> updatePos (Proxy :: Proxy TL.Text) w pos ch `shouldBe` updatePos (Proxy :: Proxy String) w pos ch context "strict Text" $ do it "unconses correctly" $ property $ \ch n -> do let p = many (char ch) :: Parsec Dec T.Text String s = replicate (getNonNegative n) ch parse p "" (T.pack s) `shouldParse` s it "updates position like with String" $ property $ \w pos ch -> updatePos (Proxy :: Proxy T.Text) w pos ch `shouldBe` updatePos (Proxy :: Proxy String) w pos ch 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 is stream correctly" $ property $ \st' ts -> forAll (incCoincidence st' ts) $ \st@State {..} -> do let p = tokens compareTokens ts :: CustomParser [Span] compareTokens x y = spanBody x == spanBody y updatePos' = updatePos (Proxy :: Proxy [Span]) stateTabWidth il = length . takeWhile id $ zipWith compareTokens stateInput ts tl = length ts consumed = take il stateInput (apos, npos) = let (pos:|z) = statePos in ( spanStart (head stateInput) :| z , foldl' (\q t -> snd (updatePos' q t)) 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 (length ts) stateInput } , Right consumed ) | otherwise -> runParser' p st `shouldBe` ( st { statePos = apos } , Left (err apos $ utoks (take (il + 1) stateInput) <> etoks ts) ) 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 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 z0' = toFirstMismatch (==) s0 s z1' = toFirstMismatch (==) s1 s prs p s `shouldFailWith` err posI (etoks s0 <> etoks s1 <> (if null s then ueof else mempty) <> (if null z0' then mempty else utoks z0') <> (if null z1' then mempty else utoks z1')) 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 Dec) ()) v = runParserT p "" "" 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 Dec String () prs p "" `shouldFailWith` err posI (cstm (DecFail 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` err posI (cstm $ DecFail msg) prs' p s `failsLeaving` s describe "ParsecT MonadIO instance" $ it "liftIO works" $ property $ \n -> do let p = liftIO (return n) :: ParsecT Dec String IO Integer runParserT p "" "" `shouldReturn` Right n describe "ParsecT MonadReader instance" $ do describe "ask" $ it "returns correct value of context" $ property $ \n -> do let p = ask :: ParsecT Dec 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 Dec 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 Dec 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 Dec 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 Dec String (Cont (Either (ParseError Char Dec) 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 Dec 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 Dec 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 "unexpected" $ it "signals correct parse error" $ property $ \item -> do let p :: MonadParsec Dec String m => m () p = void (unexpected item) grs p "" (`shouldFailWith` ParseError { errorPos = posI , errorUnexpected = E.singleton item , errorExpected = E.empty , errorCustom = E.empty }) describe "match" $ it "return consumed tokens along with the result" $ property $ \str -> do let p = match (string str) prs p str `shouldParse` (str,str) prs' p str `succeedsLeaving` "" describe "region" $ do context "when inner parser succeeds" $ it "has no effect" $ property $ \st e n -> do let p :: Parser Int p = region (const e) (pure n) runParser' p st `shouldBe` (st, Right (n :: Int)) context "when inner parser fails" $ it "the given function is used on the parse error" $ property $ \st e0 e1 -> do let p :: Parser Int p = region f $ failure (errorUnexpected e0) (errorExpected e0) (errorCustom e0) f x = ParseError { errorPos = ((G.<>) `on` errorPos) x e1 , errorUnexpected = (E.union `on` errorUnexpected) x e1 , errorExpected = (E.union `on` errorExpected) x e1 , errorCustom = (E.union `on` errorCustom) x e1 } r = ParseError { errorPos = finalPos , errorUnexpected = (E.union `on` errorUnexpected) e0 e1 , errorExpected = (E.union `on` errorExpected) e0 e1 , errorCustom = (E.union `on` errorCustom) e0 e1 } finalPos = statePos st G.<> errorPos e1 runParser' p st `shouldBe` (st { statePos = finalPos }, Left r) describe "failure" $ it "signals correct parse error" $ property $ \us ps xs -> do let p :: MonadParsec Dec String m => m () p = void (failure us ps xs) grs p "" (`shouldFailWith` ParseError { errorPos = posI , errorUnexpected = us , errorExpected = ps , errorCustom = 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 Dec 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