packages feed

parser-combinators-tests-1.3.0: tests/Control/Applicative/CombinatorsSpec.hs

{-# LANGUAGE MultiWayIf #-}

module Control.Applicative.CombinatorsSpec (spec) where

import Control.Applicative.Combinators
import Data.Char (isDigit, isLetter)
import Data.List (intersperse)
import Data.Maybe (fromJust, fromMaybe, isNothing, maybeToList)
import Test.Hspec
import Test.Hspec.Megaparsec
import Test.Hspec.Megaparsec.AdHoc
import Test.QuickCheck
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
              (length pre + n + b)
              ( 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 0 (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 0 (utok 'x' <> eeof)
          | m <= x && x <= n ->
            prs_ p s `shouldParse` s
          | x < m ->
            prs_ p s `shouldFailWith` err x (ueof <> etok 'x')
          | otherwise ->
            prs_ p s `shouldFailWith` err n (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 0 (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 1 (ueof <> etok '-')
          | c == 'a' ->
            prs_ p s `shouldFailWith` err (g n) (utok 'a' <> etok '-')
          | c == '-' && n == 0 ->
            prs_ p s `shouldFailWith` err 0 (utok '-' <> etok 'a' <> eeof)
          | c /= '-' ->
            prs_ p s
              `shouldFailWith` err
                (g n)
                ( 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 1 (ueof <> etok '-')
          | c == 'a' ->
            prs_ p s `shouldFailWith` err (g n) (utok 'a' <> etok '-')
          | c == '-' && n == 0 ->
            prs_ p s `shouldFailWith` err 0 (utok '-' <> etok 'a')
          | c /= '-' ->
            prs_ p s
              `shouldFailWith` err
                (g n)
                ( 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
              (a + b)
              (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 "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
              (a + b)
              (ueof <> etok 'c' <> elabel "letter")
        else
          let (pre, post) = break (== 'c') s
           in prs_ p s `shouldParse` ((pre, 'c'), drop 1 post)
    rightOrder (fst <$> 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 0 (ueof <> elabel "letter")
          | c == 0 ->
            prs_ p s
              `shouldFailWith` err
                (a + b)
                (ueof <> etok 'c' <> elabel "letter")
          | s == "c" ->
            prs_ p s `shouldFailWith` err 1 (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 "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 0 (ueof <> elabel "letter")
          | c == 0 ->
            prs_ p s
              `shouldFailWith` err
                (a + b)
                (ueof <> etok 'c' <> elabel "letter")
          | s == "c" ->
            prs_ p s `shouldFailWith` err 1 (ueof <> etok 'c' <> elabel "letter")
          | head s == 'c' ->
            prs_ p s `shouldParse` (("c", 'c'), drop 2 s)
          | otherwise ->
            let (pre, post) = break (== 'c') s
             in prs_ p s `shouldParse` ((pre, 'c'), drop 1 post)
    rightOrder (fst <$> 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 0 (utok c <> etok 'a' <> eeof)
          | c == '-' ->
            prs_ p s `shouldFailWith` err (length s) (ueof <> etok 'a')
          | otherwise ->
            prs_ p s `shouldFailWith` err (g n) (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 0 (ueof <> etok 'a')
          | c == 'a' && n == 0 ->
            prs_ p s `shouldParse` "a"
          | n == 0 ->
            prs_ p s `shouldFailWith` err 0 (utok c <> etok 'a')
          | c == '-' ->
            prs_ p s `shouldFailWith` err (length s) (ueof <> etok 'a')
          | otherwise ->
            prs_ p s `shouldFailWith` err (g n) (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 0 (utok c <> etok 'a' <> eeof)
          | c == '-' ->
            prs_ p s `shouldParse` a
          | otherwise ->
            prs_ p s `shouldFailWith` err (g n) (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 0 (ueof <> etok 'a')
          | c == 'a' && n == 0 ->
            prs_ p s `shouldParse` "a"
          | n == 0 ->
            prs_ p s `shouldFailWith` err 0 (utok c <> etok 'a')
          | c == '-' ->
            prs_ p s `shouldParse` a
          | otherwise ->
            prs_ p s `shouldFailWith` err (g n) (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 0 (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