bookhound-0.1.25.0: test/Bookhound/ParserSpec.hs
module Bookhound.ParserSpec where
import Test.Hspec
import Test.Hspec.QuickCheck
import Test.QuickCheck.Instances.Text ()
import Bookhound.Parser
import Data.Char
import Data.Text (pack, unpack)
spec :: Spec
spec = do
describe "Functor laws" $ do
prop "Identity" $
\x -> parse (id <$> char) x
`shouldBe`
parse char x
prop "Composition" $
\x -> parse ((isLower . toUpper) <$> char) x
`shouldBe`
parse ((isLower <$>) . (toUpper <$>) $ char) x
describe "Applicative laws" $ do
prop "Identity" $
\x -> parse (pure id <*> char) x
`shouldBe`
parse char x
prop "Homomorphism" $
\x (y :: [Int]) -> parse (pure sum <*> pure y) x
`shouldBe`
parse (pure (sum y)) x
prop "Interchange" $
\x (y :: [Int]) -> parse (pure length <*> pure y) x
`shouldBe`
parse (pure ($ y) <*> pure length) x
describe "Monad laws" $ do
prop "Left identity" $
\x -> parse (pure 'a' >>= isMatch (==) char) x
`shouldBe`
parse (isMatch (==) char 'a') x
prop "Right identity" $
\x -> parse (char >>= pure) x
`shouldBe`
parse char x
prop "Associativity" $
\x -> parse
(char >>= (\y -> isMatch (<) char y >>= isMatch (>) char)) x
`shouldBe`
parse ((char >>= isMatch (<) char) >>= isMatch (>) char) x
describe "char" $
prop "parses a single char" $
\x -> parse char x
`shouldBe`
case unpack x of
(ch : rest) -> Result (pack rest) ch
[] -> Error UnexpectedEof
describe "isMatch" $ do
prop "works for ==" $
\x y -> parse (isMatch (==) char y) x
`shouldBe`
case unpack x of
(ch : rest)
| ch == y -> Result (pack rest) ch
| otherwise -> Error $ UnexpectedChar ch
[] -> Error UnexpectedEof
prop "works for /=" $
\x y -> parse (isMatch (/=) char y) x
`shouldBe`
case unpack x of
(ch : rest)
| ch /= y -> Result (pack rest) ch
| otherwise -> Error $ UnexpectedChar ch
[] -> Error UnexpectedEof
describe "check" $
prop "performs a check on the parse result" $
\x -> parse (check "digit" isDigit char) x
`shouldBe`
case unpack x of
(ch : rest)
| isDigit ch -> Result (pack rest) ch
| otherwise -> Error $ NoMatch "digit"
[] -> Error UnexpectedEof
describe "except" $
context "when the second parser fails" $
prop "the first parser then runs" $
\x -> parse (except char (char *> char)) x
`shouldBe`
case unpack x of
[ch] -> Result (pack "") ch
(_ : _) -> Error $ NoMatch "except"
[] -> Error UnexpectedEof
describe "anyOf" $
prop "returns first parser success or last parser error if all fail" $
\x -> parse (anyOf [errorParser $ ErrorAt "firstError",
"firstSuccess" <$ char,
"secondSuccess" <$ char,
errorParser $ ErrorAt "secondError",
errorParser $ ErrorAt "lastError"]) x
`shouldBe`
case unpack x of
(_ : rest) -> Result (pack rest) "firstSuccess"
[] -> Error $ ErrorAt "lastError"
describe "allOf" $ do
context "when any parser fails" $
prop "returns first parser error" $
\x -> parse (allOf ["firstSuccess" <$ char,
"secondSuccess" <$ char,
errorParser $ ErrorAt "firstError"]) x
`shouldBe`
case unpack x of
(_ : _) -> Error $ ErrorAt "firstError"
[] -> Error UnexpectedEof
context "when all parsers succeed" $
prop "returns last parser success" $
\x -> parse (allOf ["firstSuccess" <$ char,
"secondSuccess" <$ char,
"thirdSuccess" <$ char]) x
`shouldBe`
case unpack x of
(_ : rest) -> Result (pack rest) "thirdSuccess"
[] -> Error UnexpectedEof
describe "exactly" $
prop "returns an error when some chars remain after parsing" $
\x -> parse (exactly char) x
`shouldBe`
case unpack x of
[ch] -> Result (pack []) ch
(_ : rest) -> Error $ ExpectedEof (pack rest)
[] -> Error UnexpectedEof
describe "runParser" $
prop "parses exactly and wraps the result into an either" $
\x -> runParser char x
`shouldBe`
toEither (parse (exactly char) x)
describe "withError" $
prop "includes error labels in order when running the parser" $
\x -> runParser ((,) <$> withErrorN 2 "firstErr" char
<*> withErrorN 1 "secondErr" char) x
`shouldBe`
case unpack x of
[ch1, ch2] -> Right (ch1, ch2)
(_ : _ : rest) -> Left [ErrorAt "firstErr",
ErrorAt "secondErr",
ExpectedEof (pack rest)]
_ -> Left [ErrorAt "firstErr",
ErrorAt "secondErr",
UnexpectedEof]
describe "withTransform" $
prop "transforms current parser with provided fn" $
\x -> parse (withTransform (\p -> char *> p <* char) char) x
`shouldBe`
parse (char *> char <* char) x
toEither :: ParseResult a -> Either [ParseError] a
toEither = \case
Result _ a -> Right a
Error pe -> Left [pe]