-- -- Tests for Megaparsec's primitive parser combinators. -- -- Copyright © 2015–2016 Megaparsec contributors -- -- Redistribution and use in source and binary forms, with or without -- modification, are permitted provided that the following conditions are -- met: -- -- * Redistributions of source code must retain the above copyright notice, -- this list of conditions and the following disclaimer. -- -- * Redistributions in binary form must reproduce the above copyright -- notice, this list of conditions and the following disclaimer in the -- documentation and/or other materials provided with the distribution. -- -- THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS “AS IS” AND ANY -- EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -- WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -- DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY -- DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -- DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS -- OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) -- HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, -- STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN -- ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -- POSSIBILITY OF SUCH DAMAGE. {-# 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.List (isPrefixOf, foldl') import Data.List.NonEmpty (NonEmpty (..)) import Data.Maybe (fromMaybe) 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.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.Set as E import qualified Data.Text as T import qualified Data.Text.Lazy as TL 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" $ it "updates position in stream correctly" $ property $ \st@State {..} span -> do let p = pSpan span h = head stateInput (apos, npos) = let z = NE.tail statePos in (spanStart h :| z, spanEnd h :| z) if | null stateInput -> runParser' p st `shouldBe` ( st , Left (err statePos $ ueof <> etok span) ) | spanBody h == spanBody span -> runParser' p st `shouldBe` ( st { statePos = npos , stateInput = tail stateInput } , Right span ) | otherwise -> 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 , 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 "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) 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 "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 “rest of