diff --git a/CHANGELOG.md b/CHANGELOG.md index 8f04e790..881f47c6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,24 @@ ## Megaparsec 6.4.0 +* `Text.Megaparsec` now re-exports `Control.Monad.Combinators` instead of + `Control.Applicative.Combinators` from `parser-combinators` because the + monadic counterparts of the familiar combinators are more efficient and + not as leaky. + + This may cause minor breakage in certain cases: + + * You import `Control.Applicative` and in that case there will be a name + conflict between `Control.Applicative.many` and + `Control.Monad.Combinator.many` now (the same for `some`). + + * You define a polymorphic helper in terms of combinator(s) from + `Control.Applicative.Combinators` and use `Applicative` or `Alternative` + constraint. In this case you'll have to adjust the constraint to be + `Monad` or `MonadPlus` respectively. + + Also note that the new `Control.Monad.Combinators` module we re-export now + re-exports `empty` from `Control.Applicative`. + * Fix the `atEnd` parser. It now does not produce hints, so when you use it, it won't contribute to the “expecting end of input” component of parse error. diff --git a/Text/Megaparsec.hs b/Text/Megaparsec.hs index 27bc97dc..9591a973 100644 --- a/Text/Megaparsec.hs +++ b/Text/Megaparsec.hs @@ -77,7 +77,7 @@ module Text.Megaparsec module Text.Megaparsec.Pos , module Text.Megaparsec.Error , module Text.Megaparsec.Stream - , module Control.Applicative.Combinators + , module Control.Monad.Combinators -- * Data types , State (..) , Parsec @@ -118,9 +118,9 @@ module Text.Megaparsec , dbg ) where -import Control.Applicative.Combinators import Control.DeepSeq import Control.Monad +import Control.Monad.Combinators import Control.Monad.Cont.Class import Control.Monad.Error.Class import Control.Monad.Identity diff --git a/Text/Megaparsec/Perm.hs b/Text/Megaparsec/Perm.hs index 84786530..e5f4b841 100644 --- a/Text/Megaparsec/Perm.hs +++ b/Text/Megaparsec/Perm.hs @@ -29,7 +29,7 @@ where import Text.Megaparsec #if !MIN_VERSION_base(4,8,0) -import Control.Applicative ((<$>), (<*>)) +import Control.Applicative #endif infixl 1 <||>, <|?> @@ -61,10 +61,11 @@ data Branch s m a = forall b. Branch (PermParser s m (b -> a)) (m b) makePermParser :: MonadParsec e s m => PermParser s m a -- ^ Given permutation parser -> m a -- ^ Normal parser built from it -makePermParser (Perm def xs) = choice (fmap branch xs ++ empty) - where empty = case def of - Nothing -> [] - Just x -> [return x] +makePermParser (Perm def xs) = choice (fmap branch xs ++ empty') + where empty' = + case def of + Nothing -> [] + Just x -> [return x] branch (Branch perm p) = flip ($) <$> p <*> makePermParser perm -- | The expression @f \<$$> p@ creates a fresh permutation parser diff --git a/megaparsec.cabal b/megaparsec.cabal index ee9482ed..916a931a 100644 --- a/megaparsec.cabal +++ b/megaparsec.cabal @@ -40,7 +40,7 @@ library , containers >= 0.5 && < 0.6 , deepseq >= 1.3 && < 1.5 , mtl >= 2.0 && < 3.0 - , parser-combinators >= 0.1 && < 1.0 + , parser-combinators >= 0.4 && < 1.0 , scientific >= 0.3.1 && < 0.4 , text >= 0.2 && < 1.3 , transformers >= 0.4 && < 0.6 @@ -75,6 +75,7 @@ test-suite tests else ghc-options: -O2 -Wall other-modules: Control.Applicative.CombinatorsSpec + , Control.Monad.CombinatorsSpec , Test.Hspec.Megaparsec , Test.Hspec.Megaparsec.AdHoc , Text.Megaparsec.Byte.LexerSpec diff --git a/stack.yaml b/stack.yaml index d3afca89..66dde90b 100644 --- a/stack.yaml +++ b/stack.yaml @@ -1,3 +1,5 @@ resolver: lts-10.0 packages: - '.' +extra-deps: +- parser-combinators-0.4.0 diff --git a/tests/Control/Applicative/CombinatorsSpec.hs b/tests/Control/Applicative/CombinatorsSpec.hs index f9c6bb43..2c3c9e1b 100644 --- a/tests/Control/Applicative/CombinatorsSpec.hs +++ b/tests/Control/Applicative/CombinatorsSpec.hs @@ -2,7 +2,6 @@ module Control.Applicative.CombinatorsSpec (spec) where -import Control.Applicative import Data.Char (isLetter, isDigit) import Data.List (intersperse) import Data.Maybe (fromMaybe, maybeToList, isNothing, fromJust) @@ -17,225 +16,261 @@ import Text.Megaparsec.Char spec :: Spec spec = do - describe "between" . it "works" . property $ \pre c n' post -> do - let p = between (string pre) (string post) (many (char c)) - n = getNonNegative n' - b = length (takeWhile (== c) post) - z = replicate n c - s = pre ++ z ++ post - if b > 0 - then prs_ p s `shouldFailWith` err (posN (length pre + n + b) s) - ( etoks post <> etok c <> - if length post == b - then ueof - else utoks (drop b post) ) - else prs_ p s `shouldParse` z - - describe "choice" . it "works" . property $ \cs' s' -> do - let cs = getNonEmpty cs' - p = choice (char <$> cs) - s = [s'] - if s' `elem` cs - then prs_ p s `shouldParse` s' - else prs_ p s `shouldFailWith` err posI (utok s' <> mconcat (etok <$> cs)) - - describe "count" . it "works" . property $ \n x' -> do - let x = getNonNegative x' - p = count n (char 'x') - p' = count' n n (char 'x') - s = replicate x 'x' - prs_ p s `shouldBe` prs_ p' s - - describe "count'" . it "works" . property $ \m n x' -> do - let x = getNonNegative x' - p = count' m n (char 'x') - s = replicate x 'x' - if | n <= 0 || m > n -> - if x == 0 - then prs_ p s `shouldParse` "" - else prs_ p s `shouldFailWith` err posI (utok 'x' <> eeof) - | m <= x && x <= n -> - prs_ p s `shouldParse` s - | x < m -> - prs_ p s `shouldFailWith` err (posN x s) (ueof <> etok 'x') - | otherwise -> - prs_ p s `shouldFailWith` err (posN n s) (utok 'x' <> eeof) - - describe "eitherP" . it "works" . property $ \ch -> do - let p = eitherP letterChar digitChar - s = pure ch - if | isLetter ch -> prs_ p s `shouldParse` Left ch - | isDigit ch -> prs_ p s `shouldParse` Right ch - | otherwise -> prs_ p s `shouldFailWith` - err posI (utok ch <> elabel "letter" <> elabel "digit") - - describe "endBy" . it "works" . property $ \n' c -> do - let n = getNonNegative n' - p = endBy (char 'a') (char '-') - s = intersperse '-' (replicate n 'a') ++ [c] - if | c == 'a' && n == 0 -> - prs_ p s `shouldFailWith` err (posN (1 :: Int) s) (ueof <> etok '-') - | c == 'a' -> - prs_ p s `shouldFailWith` err (posN (g n) s) (utok 'a' <> etok '-') - | c == '-' && n == 0 -> - prs_ p s `shouldFailWith` err posI (utok '-' <> etok 'a'<> eeof) - | c /= '-' -> - prs_ p s `shouldFailWith` err (posN (g n) s) - ( utok c <> - (if n > 0 then etok '-' else eeof) <> - (if n == 0 then etok 'a' else mempty) ) - | otherwise -> prs_ p s `shouldParse` replicate n 'a' - - describe "endBy1" . it "works" . property $ \n' c -> do - let n = getNonNegative n' - p = endBy1 (char 'a') (char '-') - s = intersperse '-' (replicate n 'a') ++ [c] - if | c == 'a' && n == 0 -> - prs_ p s `shouldFailWith` err (posN (1 :: Int) s) (ueof <> etok '-') - | c == 'a' -> - prs_ p s `shouldFailWith` err (posN (g n) s) (utok 'a' <> etok '-') - | c == '-' && n == 0 -> - prs_ p s `shouldFailWith` err posI (utok '-' <> etok 'a') - | c /= '-' -> - prs_ p s `shouldFailWith` err (posN (g n) s) - ( utok c <> - (if n > 0 then etok '-' else mempty) <> - (if n == 0 then etok 'a' else mempty) ) - | otherwise -> prs_ p s `shouldParse` replicate n 'a' - - describe "manyTill" . it "works" . property $ \a' b' c' -> do - let [a,b,c] = getNonNegative <$> [a',b',c'] - p = (,) <$> manyTill letterChar (char 'c') <*> many letterChar - s = abcRow a b c - if c == 0 - then prs_ p s `shouldFailWith` err (posN (a + b) s) - (ueof <> etok 'c' <> elabel "letter") - else let (pre, post) = break (== 'c') s + describe "between" $ + it "works" . property $ \pre c n' post -> do + let p = between (string pre) (string post) (many (char c)) + n = getNonNegative n' + b = length (takeWhile (== c) post) + z = replicate n c + s = pre ++ z ++ post + if b > 0 + then prs_ p s `shouldFailWith` err (posN (length pre + n + b) s) + ( etoks post <> etok c <> + if length post == b + then ueof + else utoks (drop b post) ) + else prs_ p s `shouldParse` z + + describe "choice" $ + it "works" . property $ \cs' s' -> do + let cs = getNonEmpty cs' + p = choice (char <$> cs) + s = [s'] + if s' `elem` cs + then prs_ p s `shouldParse` s' + else prs_ p s `shouldFailWith` err posI (utok s' <> mconcat (etok <$> cs)) + + describe "count" $ do + it "works" . property $ \n x' -> do + let x = getNonNegative x' + p = count n (char 'x') + p' = count' n n (char 'x') + s = replicate x 'x' + prs_ p s `shouldBe` prs_ p' s + rightOrder (count 3 letterChar) "abc" "abc" + + describe "count'" $ do + it "works" . property $ \m n x' -> do + let x = getNonNegative x' + p = count' m n (char 'x') + s = replicate x 'x' + if | n <= 0 || m > n -> + if x == 0 + then prs_ p s `shouldParse` "" + else prs_ p s `shouldFailWith` err posI (utok 'x' <> eeof) + | m <= x && x <= n -> + prs_ p s `shouldParse` s + | x < m -> + prs_ p s `shouldFailWith` err (posN x s) (ueof <> etok 'x') + | otherwise -> + prs_ p s `shouldFailWith` err (posN n s) (utok 'x' <> eeof) + rightOrder (count' 1 3 letterChar) "abc" "abc" + + describe "eitherP" $ + it "works" . property $ \ch -> do + let p = eitherP letterChar digitChar + s = pure ch + if | isLetter ch -> prs_ p s `shouldParse` Left ch + | isDigit ch -> prs_ p s `shouldParse` Right ch + | otherwise -> prs_ p s `shouldFailWith` + err posI (utok ch <> elabel "letter" <> elabel "digit") + + describe "endBy" $ do + it "works" . property $ \n' c -> do + let n = getNonNegative n' + p = endBy (char 'a') (char '-') + s = intersperse '-' (replicate n 'a') ++ [c] + if | c == 'a' && n == 0 -> + prs_ p s `shouldFailWith` err (posN (1 :: Int) s) (ueof <> etok '-') + | c == 'a' -> + prs_ p s `shouldFailWith` err (posN (g n) s) (utok 'a' <> etok '-') + | c == '-' && n == 0 -> + prs_ p s `shouldFailWith` err posI (utok '-' <> etok 'a'<> eeof) + | c /= '-' -> + prs_ p s `shouldFailWith` err (posN (g n) s) + ( utok c <> + (if n > 0 then etok '-' else eeof) <> + (if n == 0 then etok 'a' else mempty) ) + | otherwise -> prs_ p s `shouldParse` replicate n 'a' + rightOrder (endBy letterChar (char ',')) "a,b,c," "abc" + + describe "endBy1" $ do + it "works" . property $ \n' c -> do + let n = getNonNegative n' + p = endBy1 (char 'a') (char '-') + s = intersperse '-' (replicate n 'a') ++ [c] + if | c == 'a' && n == 0 -> + prs_ p s `shouldFailWith` err (posN (1 :: Int) s) (ueof <> etok '-') + | c == 'a' -> + prs_ p s `shouldFailWith` err (posN (g n) s) (utok 'a' <> etok '-') + | c == '-' && n == 0 -> + prs_ p s `shouldFailWith` err posI (utok '-' <> etok 'a') + | c /= '-' -> + prs_ p s `shouldFailWith` err (posN (g n) s) + ( utok c <> + (if n > 0 then etok '-' else mempty) <> + (if n == 0 then etok 'a' else mempty) ) + | otherwise -> prs_ p s `shouldParse` replicate n 'a' + rightOrder (endBy1 letterChar (char ',')) "a,b,c," "abc" + + describe "manyTill" $ do + it "works" . property $ \a' b' c' -> do + let [a,b,c] = getNonNegative <$> [a',b',c'] + p = (,) <$> manyTill letterChar (char 'c') <*> many letterChar + s = abcRow a b c + if c == 0 + then prs_ p s `shouldFailWith` err (posN (a + b) s) + (ueof <> etok 'c' <> elabel "letter") + else let (pre, post) = break (== 'c') s + in prs_ p s `shouldParse` (pre, drop 1 post) + rightOrder (manyTill letterChar (char 'd')) "abcd" "abc" + + describe "someTill" $ do + it "works" . property $ \a' b' c' -> do + let [a,b,c] = getNonNegative <$> [a',b',c'] + p = (,) <$> someTill letterChar (char 'c') <*> many letterChar + s = abcRow a b c + if | null s -> + prs_ p s `shouldFailWith` err posI (ueof <> elabel "letter") + | c == 0 -> + prs_ p s `shouldFailWith` err (posN (a + b) s) + (ueof <> etok 'c' <> elabel "letter") + | s == "c" -> + prs_ p s `shouldFailWith` err + (posN (1 :: Int) s) (ueof <> etok 'c' <> elabel "letter") + | head s == 'c' -> + prs_ p s `shouldParse` ("c", drop 2 s) + | otherwise -> + let (pre, post) = break (== 'c') s in prs_ p s `shouldParse` (pre, drop 1 post) + rightOrder (someTill letterChar (char 'd')) "abcd" "abc" + + describe "option" $ + it "works" . property $ \d a s -> do + let p = option d (string a) + p' = fromMaybe d <$> optional (string a) + prs_ p s `shouldBe` prs_ p' s + + describe "sepBy" $ do + it "works" . property $ \n' c' -> do + let n = getNonNegative n' + c = fromJust c' + p = sepBy (char 'a') (char '-') + s = intersperse '-' (replicate n 'a') ++ maybeToList c' + if | isNothing c' -> + prs_ p s `shouldParse` replicate n 'a' + | c == 'a' && n == 0 -> + prs_ p s `shouldParse` "a" + | n == 0 -> + prs_ p s `shouldFailWith` err posI + (utok c <> etok 'a' <> eeof) + | c == '-' -> + prs_ p s `shouldFailWith` err (posN (length s) s) + (ueof <> etok 'a') + | otherwise -> + prs_ p s `shouldFailWith` err (posN (g n) s) + (utok c <> etok '-' <> eeof) + rightOrder (sepBy letterChar (char ',')) "a,b,c" "abc" + + describe "sepBy1" $ do + it "works" . property $ \n' c' -> do + let n = getNonNegative n' + c = fromJust c' + p = sepBy1 (char 'a') (char '-') + s = intersperse '-' (replicate n 'a') ++ maybeToList c' + if | isNothing c' && n >= 1 -> + prs_ p s `shouldParse` replicate n 'a' + | isNothing c' -> + prs_ p s `shouldFailWith` err posI (ueof <> etok 'a') + | c == 'a' && n == 0 -> + prs_ p s `shouldParse` "a" + | n == 0 -> + prs_ p s `shouldFailWith` err posI (utok c <> etok 'a') + | c == '-' -> + prs_ p s `shouldFailWith` err (posN (length s) s) (ueof <> etok 'a') + | otherwise -> + prs_ p s `shouldFailWith` err (posN (g n) s) (utok c <> etok '-' <> eeof) + rightOrder (sepBy1 letterChar (char ',')) "a,b,c" "abc" + + describe "sepEndBy" $ do + it "works" . property $ \n' c' -> do + let n = getNonNegative n' + c = fromJust c' + p = sepEndBy (char 'a') (char '-') + a = replicate n 'a' + s = intersperse '-' (replicate n 'a') ++ maybeToList c' + if | isNothing c' -> + prs_ p s `shouldParse` a + | c == 'a' && n == 0 -> + prs_ p s `shouldParse` "a" + | n == 0 -> + prs_ p s `shouldFailWith` err posI (utok c <> etok 'a' <> eeof) + | c == '-' -> + prs_ p s `shouldParse` a + | otherwise -> + prs_ p s `shouldFailWith` err (posN (g n) s) (utok c <> etok '-' <> eeof) + rightOrder (sepEndBy letterChar (char ',')) "a,b,c," "abc" + + describe "sepEndBy1" $ do + it "works" . property $ \n' c' -> do + let n = getNonNegative n' + c = fromJust c' + p = sepEndBy1 (char 'a') (char '-') + a = replicate n 'a' + s = intersperse '-' (replicate n 'a') ++ maybeToList c' + if | isNothing c' && n >= 1 -> + prs_ p s `shouldParse` a + | isNothing c' -> + prs_ p s `shouldFailWith` err posI (ueof <> etok 'a') + | c == 'a' && n == 0 -> + prs_ p s `shouldParse` "a" + | n == 0 -> + prs_ p s `shouldFailWith` err posI (utok c <> etok 'a') + | c == '-' -> + prs_ p s `shouldParse` a + | otherwise -> + prs_ p s `shouldFailWith` err (posN (g n) s) (utok c <> etok '-' <> eeof) + rightOrder (sepEndBy1 letterChar (char ',')) "a,b,c," "abc" + + describe "skipMany" $ + it "works" . property $ \c n' a -> do + let p = skipMany (char c) *> string a + n = getNonNegative n' + p' = many (char c) >> string a + s = replicate n c ++ a + prs_ p s `shouldBe` prs_ p' s + + describe "skipSome" $ + it "works" . property $ \c n' a -> do + let p = skipSome (char c) *> string a + n = getNonNegative n' + p' = some (char c) >> string a + s = replicate n c ++ a + prs_ p s `shouldBe` prs_ p' s + + describe "skipCount" $ + it "works" . property $ \c n' a -> do + let p = skipCount n (char c) *> string a + n = getNonNegative n' + p' = count n (char c) *> string a + s = replicate n c ++ a + prs_ p s `shouldBe` prs_ p' s + + describe "skipManyTill" $ + it "works" . property $ \c n' a -> c /= a ==> do + let p = skipManyTill (char c) (char a) + n = getNonNegative n' + s = replicate n c ++ [a] + prs_ p s `shouldParse` a - describe "someTill" . it "works" . property $ \a' b' c' -> do - let [a,b,c] = getNonNegative <$> [a',b',c'] - p = (,) <$> someTill letterChar (char 'c') <*> many letterChar - s = abcRow a b c - if | null s -> - prs_ p s `shouldFailWith` err posI (ueof <> elabel "letter") - | c == 0 -> - prs_ p s `shouldFailWith` err (posN (a + b) s) - (ueof <> etok 'c' <> elabel "letter") - | s == "c" -> - prs_ p s `shouldFailWith` err - (posN (1 :: Int) s) (ueof <> etok 'c' <> elabel "letter") - | head s == 'c' -> - prs_ p s `shouldParse` ("c", drop 2 s) - | otherwise -> - let (pre, post) = break (== 'c') s - in prs_ p s `shouldParse` (pre, drop 1 post) - - describe "option" . it "works" . property $ \d a s -> do - let p = option d (string a) - p' = fromMaybe d <$> optional (string a) - prs_ p s `shouldBe` prs_ p' s - - describe "sepBy" . it "works" . property $ \n' c' -> do - let n = getNonNegative n' - c = fromJust c' - p = sepBy (char 'a') (char '-') - s = intersperse '-' (replicate n 'a') ++ maybeToList c' - if | isNothing c' -> - prs_ p s `shouldParse` replicate n 'a' - | c == 'a' && n == 0 -> - prs_ p s `shouldParse` "a" - | n == 0 -> - prs_ p s `shouldFailWith` err posI - (utok c <> etok 'a' <> eeof) - | c == '-' -> - prs_ p s `shouldFailWith` err (posN (length s) s) - (ueof <> etok 'a') - | otherwise -> - prs_ p s `shouldFailWith` err (posN (g n) s) - (utok c <> etok '-' <> eeof) - - describe "sepBy1" . it "works" . property $ \n' c' -> do - let n = getNonNegative n' - c = fromJust c' - p = sepBy1 (char 'a') (char '-') - s = intersperse '-' (replicate n 'a') ++ maybeToList c' - if | isNothing c' && n >= 1 -> - prs_ p s `shouldParse` replicate n 'a' - | isNothing c' -> - prs_ p s `shouldFailWith` err posI (ueof <> etok 'a') - | c == 'a' && n == 0 -> - prs_ p s `shouldParse` "a" - | n == 0 -> - prs_ p s `shouldFailWith` err posI (utok c <> etok 'a') - | c == '-' -> - prs_ p s `shouldFailWith` err (posN (length s) s) (ueof <> etok 'a') - | otherwise -> - prs_ p s `shouldFailWith` err (posN (g n) s) (utok c <> etok '-' <> eeof) - - describe "sepEndBy" . it "works" . property $ \n' c' -> do - let n = getNonNegative n' - c = fromJust c' - p = sepEndBy (char 'a') (char '-') - a = replicate n 'a' - s = intersperse '-' (replicate n 'a') ++ maybeToList c' - if | isNothing c' -> - prs_ p s `shouldParse` a - | c == 'a' && n == 0 -> - prs_ p s `shouldParse` "a" - | n == 0 -> - prs_ p s `shouldFailWith` err posI (utok c <> etok 'a' <> eeof) - | c == '-' -> - prs_ p s `shouldParse` a - | otherwise -> - prs_ p s `shouldFailWith` err (posN (g n) s) (utok c <> etok '-' <> eeof) - - describe "sepEndBy1" . it "works" . property $ \n' c' -> do - let n = getNonNegative n' - c = fromJust c' - p = sepEndBy1 (char 'a') (char '-') - a = replicate n 'a' - s = intersperse '-' (replicate n 'a') ++ maybeToList c' - if | isNothing c' && n >= 1 -> - prs_ p s `shouldParse` a - | isNothing c' -> - prs_ p s `shouldFailWith` err posI (ueof <> etok 'a') - | c == 'a' && n == 0 -> - prs_ p s `shouldParse` "a" - | n == 0 -> - prs_ p s `shouldFailWith` err posI (utok c <> etok 'a') - | c == '-' -> - prs_ p s `shouldParse` a - | otherwise -> - prs_ p s `shouldFailWith` err (posN (g n) s) (utok c <> etok '-' <> eeof) - - describe "skipMany" . it "works" . property $ \c n' a -> do - let p = skipMany (char c) *> string a - n = getNonNegative n' - p' = many (char c) >> string a - s = replicate n c ++ a - prs_ p s `shouldBe` prs_ p' s - - describe "skipSome" . it "works" . property $ \c n' a -> do - let p = skipSome (char c) *> string a - n = getNonNegative n' - p' = some (char c) >> string a - s = replicate n c ++ a - prs_ p s `shouldBe` prs_ p' s - - describe "skipManyTill" . it "works" . property $ \c n' a -> c /= a ==> do - let p = skipManyTill (char c) (char a) - n = getNonNegative n' - s = replicate n c ++ [a] - prs_ p s `shouldParse` a - - describe "skipSomeTill" . it "works" . property $ \c n' a -> c /= a ==> do - let p = skipSomeTill (char c) (char a) - n = getNonNegative n' - s = replicate n c ++ [a] - if n == 0 - then prs_ p s `shouldFailWith` err posI (utok a <> etok c) - else prs_ p s `shouldParse` a + describe "skipSomeTill" $ + it "works" . property $ \c n' a -> c /= a ==> do + let p = skipSomeTill (char c) (char a) + n = getNonNegative n' + s = replicate n c ++ [a] + if n == 0 + then prs_ p s `shouldFailWith` err posI (utok a <> etok c) + else prs_ p s `shouldParse` a ---------------------------------------------------------------------------- -- Helpers diff --git a/tests/Control/Monad/CombinatorsSpec.hs b/tests/Control/Monad/CombinatorsSpec.hs new file mode 100644 index 00000000..4211f63c --- /dev/null +++ b/tests/Control/Monad/CombinatorsSpec.hs @@ -0,0 +1,239 @@ +{-# LANGUAGE MultiWayIf #-} + +module Control.Monad.CombinatorsSpec (spec) where + +import Data.List (intersperse) +import Data.Maybe (maybeToList, isNothing, fromJust) +import Data.Monoid +import Test.Hspec +import Test.Hspec.Megaparsec +import Test.Hspec.Megaparsec.AdHoc +import Test.QuickCheck +import Text.Megaparsec +import Text.Megaparsec.Char + +spec :: Spec +spec = do + + describe "count" $ do + it "works" . property $ \n x' -> do + let x = getNonNegative x' + p = count n (char 'x') + p' = count' n n (char 'x') + s = replicate x 'x' + prs_ p s `shouldBe` prs_ p' s + rightOrder (count 3 letterChar) "abc" "abc" + + describe "count'" $ do + it "works" . property $ \m n x' -> do + let x = getNonNegative x' + p = count' m n (char 'x') + s = replicate x 'x' + if | n <= 0 || m > n -> + if x == 0 + then prs_ p s `shouldParse` "" + else prs_ p s `shouldFailWith` err posI (utok 'x' <> eeof) + | m <= x && x <= n -> + prs_ p s `shouldParse` s + | x < m -> + prs_ p s `shouldFailWith` err (posN x s) (ueof <> etok 'x') + | otherwise -> + prs_ p s `shouldFailWith` err (posN n s) (utok 'x' <> eeof) + rightOrder (count' 1 3 letterChar) "abc" "abc" + + describe "endBy" $ do + it "works" . property $ \n' c -> do + let n = getNonNegative n' + p = endBy (char 'a') (char '-') + s = intersperse '-' (replicate n 'a') ++ [c] + if | c == 'a' && n == 0 -> + prs_ p s `shouldFailWith` err (posN (1 :: Int) s) (ueof <> etok '-') + | c == 'a' -> + prs_ p s `shouldFailWith` err (posN (g n) s) (utok 'a' <> etok '-') + | c == '-' && n == 0 -> + prs_ p s `shouldFailWith` err posI (utok '-' <> etok 'a'<> eeof) + | c /= '-' -> + prs_ p s `shouldFailWith` err (posN (g n) s) + ( utok c <> + (if n > 0 then etok '-' else eeof) <> + (if n == 0 then etok 'a' else mempty) ) + | otherwise -> prs_ p s `shouldParse` replicate n 'a' + rightOrder (endBy letterChar (char ',')) "a,b,c," "abc" + + describe "endBy1" $ do + it "works" . property $ \n' c -> do + let n = getNonNegative n' + p = endBy1 (char 'a') (char '-') + s = intersperse '-' (replicate n 'a') ++ [c] + if | c == 'a' && n == 0 -> + prs_ p s `shouldFailWith` err (posN (1 :: Int) s) (ueof <> etok '-') + | c == 'a' -> + prs_ p s `shouldFailWith` err (posN (g n) s) (utok 'a' <> etok '-') + | c == '-' && n == 0 -> + prs_ p s `shouldFailWith` err posI (utok '-' <> etok 'a') + | c /= '-' -> + prs_ p s `shouldFailWith` err (posN (g n) s) + ( utok c <> + (if n > 0 then etok '-' else mempty) <> + (if n == 0 then etok 'a' else mempty) ) + | otherwise -> prs_ p s `shouldParse` replicate n 'a' + rightOrder (endBy1 letterChar (char ',')) "a,b,c," "abc" + + describe "manyTill" $ do + it "works" . property $ \a' b' c' -> do + let [a,b,c] = getNonNegative <$> [a',b',c'] + p = (,) <$> manyTill letterChar (char 'c') <*> many letterChar + s = abcRow a b c + if c == 0 + then prs_ p s `shouldFailWith` err (posN (a + b) s) + (ueof <> etok 'c' <> elabel "letter") + else let (pre, post) = break (== 'c') s + in prs_ p s `shouldParse` (pre, drop 1 post) + rightOrder (manyTill letterChar (char 'd')) "abcd" "abc" + + describe "someTill" $ do + it "works" . property $ \a' b' c' -> do + let [a,b,c] = getNonNegative <$> [a',b',c'] + p = (,) <$> someTill letterChar (char 'c') <*> many letterChar + s = abcRow a b c + if | null s -> + prs_ p s `shouldFailWith` err posI (ueof <> elabel "letter") + | c == 0 -> + prs_ p s `shouldFailWith` err (posN (a + b) s) + (ueof <> etok 'c' <> elabel "letter") + | s == "c" -> + prs_ p s `shouldFailWith` err + (posN (1 :: Int) s) (ueof <> etok 'c' <> elabel "letter") + | head s == 'c' -> + prs_ p s `shouldParse` ("c", drop 2 s) + | otherwise -> + let (pre, post) = break (== 'c') s + in prs_ p s `shouldParse` (pre, drop 1 post) + rightOrder (someTill letterChar (char 'd')) "abcd" "abc" + + describe "sepBy" $ do + it "works" . property $ \n' c' -> do + let n = getNonNegative n' + c = fromJust c' + p = sepBy (char 'a') (char '-') + s = intersperse '-' (replicate n 'a') ++ maybeToList c' + if | isNothing c' -> + prs_ p s `shouldParse` replicate n 'a' + | c == 'a' && n == 0 -> + prs_ p s `shouldParse` "a" + | n == 0 -> + prs_ p s `shouldFailWith` err posI + (utok c <> etok 'a' <> eeof) + | c == '-' -> + prs_ p s `shouldFailWith` err (posN (length s) s) + (ueof <> etok 'a') + | otherwise -> + prs_ p s `shouldFailWith` err (posN (g n) s) + (utok c <> etok '-' <> eeof) + rightOrder (sepBy letterChar (char ',')) "a,b,c" "abc" + + describe "sepBy1" $ do + it "works" . property $ \n' c' -> do + let n = getNonNegative n' + c = fromJust c' + p = sepBy1 (char 'a') (char '-') + s = intersperse '-' (replicate n 'a') ++ maybeToList c' + if | isNothing c' && n >= 1 -> + prs_ p s `shouldParse` replicate n 'a' + | isNothing c' -> + prs_ p s `shouldFailWith` err posI (ueof <> etok 'a') + | c == 'a' && n == 0 -> + prs_ p s `shouldParse` "a" + | n == 0 -> + prs_ p s `shouldFailWith` err posI (utok c <> etok 'a') + | c == '-' -> + prs_ p s `shouldFailWith` err (posN (length s) s) (ueof <> etok 'a') + | otherwise -> + prs_ p s `shouldFailWith` err (posN (g n) s) (utok c <> etok '-' <> eeof) + rightOrder (sepBy1 letterChar (char ',')) "a,b,c" "abc" + + describe "sepEndBy" $ do + it "works" . property $ \n' c' -> do + let n = getNonNegative n' + c = fromJust c' + p = sepEndBy (char 'a') (char '-') + a = replicate n 'a' + s = intersperse '-' (replicate n 'a') ++ maybeToList c' + if | isNothing c' -> + prs_ p s `shouldParse` a + | c == 'a' && n == 0 -> + prs_ p s `shouldParse` "a" + | n == 0 -> + prs_ p s `shouldFailWith` err posI (utok c <> etok 'a' <> eeof) + | c == '-' -> + prs_ p s `shouldParse` a + | otherwise -> + prs_ p s `shouldFailWith` err (posN (g n) s) (utok c <> etok '-' <> eeof) + rightOrder (sepEndBy letterChar (char ',')) "a,b,c," "abc" + + describe "sepEndBy1" $ do + it "works" . property $ \n' c' -> do + let n = getNonNegative n' + c = fromJust c' + p = sepEndBy1 (char 'a') (char '-') + a = replicate n 'a' + s = intersperse '-' (replicate n 'a') ++ maybeToList c' + if | isNothing c' && n >= 1 -> + prs_ p s `shouldParse` a + | isNothing c' -> + prs_ p s `shouldFailWith` err posI (ueof <> etok 'a') + | c == 'a' && n == 0 -> + prs_ p s `shouldParse` "a" + | n == 0 -> + prs_ p s `shouldFailWith` err posI (utok c <> etok 'a') + | c == '-' -> + prs_ p s `shouldParse` a + | otherwise -> + prs_ p s `shouldFailWith` err (posN (g n) s) (utok c <> etok '-' <> eeof) + rightOrder (sepEndBy1 letterChar (char ',')) "a,b,c," "abc" + + describe "skipMany" $ + it "works" . property $ \c n' a -> do + let p = skipMany (char c) *> string a + n = getNonNegative n' + p' = many (char c) >> string a + s = replicate n c ++ a + prs_ p s `shouldBe` prs_ p' s + + describe "skipSome" $ + it "works" . property $ \c n' a -> do + let p = skipSome (char c) *> string a + n = getNonNegative n' + p' = some (char c) >> string a + s = replicate n c ++ a + prs_ p s `shouldBe` prs_ p' s + + describe "skipCount" $ + it "works" . property $ \c n' a -> do + let p = skipCount n (char c) *> string a + n = getNonNegative n' + p' = count n (char c) *> string a + s = replicate n c ++ a + prs_ p s `shouldBe` prs_ p' s + + describe "skipManyTill" $ + it "works" . property $ \c n' a -> c /= a ==> do + let p = skipManyTill (char c) (char a) + n = getNonNegative n' + s = replicate n c ++ [a] + prs_ p s `shouldParse` a + + describe "skipSomeTill" $ + it "works" . property $ \c n' a -> c /= a ==> do + let p = skipSomeTill (char c) (char a) + n = getNonNegative n' + s = replicate n c ++ [a] + if n == 0 + then prs_ p s `shouldFailWith` err posI (utok a <> etok c) + else prs_ p s `shouldParse` a + +---------------------------------------------------------------------------- +-- Helpers + +g :: Int -> Int +g x = x + if x > 0 then x - 1 else 0 diff --git a/tests/Test/Hspec/Megaparsec/AdHoc.hs b/tests/Test/Hspec/Megaparsec/AdHoc.hs index e1522c5b..fc6e3a87 100644 --- a/tests/Test/Hspec/Megaparsec/AdHoc.hs +++ b/tests/Test/Hspec/Megaparsec/AdHoc.hs @@ -17,6 +17,7 @@ module Test.Hspec.Megaparsec.AdHoc -- * Other , abcRow , toFirstMismatch + , rightOrder , Parser ) where @@ -170,6 +171,17 @@ toFirstMismatch toFirstMismatch f str s = take (n + 1) s where n = length (takeWhile (uncurry f) (zip str s)) +-- | Check that the given parser returns the list in the right order. + +rightOrder + :: Parser String -- ^ The parser to test + -> String -- ^ Input for the parser + -> String -- ^ Expected result + -> Spec +rightOrder p s s' = + it "produces the list in the right order" $ + prs_ p s `shouldParse` s' + -- | The type of parser that consumes a 'String'. type Parser = Parsec Void String diff --git a/tests/Text/Megaparsec/Char/LexerSpec.hs b/tests/Text/Megaparsec/Char/LexerSpec.hs index 7997dbcb..e58a4db3 100644 --- a/tests/Text/Megaparsec/Char/LexerSpec.hs +++ b/tests/Text/Megaparsec/Char/LexerSpec.hs @@ -6,7 +6,6 @@ module Text.Megaparsec.Char.LexerSpec (spec) where -import Control.Applicative import Control.Monad import Data.Char hiding (ord) import Data.List (isInfixOf) diff --git a/tests/Text/MegaparsecSpec.hs b/tests/Text/MegaparsecSpec.hs index 2a63aab0..beb141ed 100644 --- a/tests/Text/MegaparsecSpec.hs +++ b/tests/Text/MegaparsecSpec.hs @@ -11,7 +11,6 @@ module Text.MegaparsecSpec (spec) where -import Control.Applicative import Control.Monad.Cont import Control.Monad.Except import Control.Monad.Identity