{-# LANGUAGE RankNTypes #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE TypeApplications #-} {-# LANGUAGE FlexibleContexts #-} module Main (main) where import Control.Monad (replicateM, replicateM_) import Data.ByteString (ByteString) import Data.Either (isLeft) import Data.Text (Text) import GHC.Stack (HasCallStack) import Prelude hiding (getLine) import System.Random import Test.Tasty import Test.Tasty.HUnit import Text.PariPari import Text.PariPari.Internal.Chunk (stringToChunk, asc_a, asc_0, asc_9) import qualified Data.Char as C import qualified Data.List.NonEmpty as NE main :: IO () main = defaultMain tests randomTries :: Int randomTries = 1000 randomStringLen :: Int randomStringLen = 1000 runReporterEither :: Chunk k => Reporter k a -> FilePath -> k -> Either [Report] a runReporterEither p f k = case runReporter p f k of (Just a, []) -> Right a (_, r) -> Left r -- Only generate valid Unicode characters randomChar :: IO Char randomChar = do c <- randomIO let n = C.ord c if n == 0 || (n >= 0xD800 && n <= 0xDFFF) || n > 0x10FFFF then randomChar else pure c randomString :: IO String randomString = do n <- randomRIO (1, randomStringLen) replicateM n randomChar randomAsciiString :: IO String randomAsciiString = do n <- randomRIO (1, randomStringLen) replicateM n (randomRIO (C.chr 1, C.chr 127)) tests :: TestTree tests = testGroup "Tests" [ testGroup "Chunk" [ testGroup "Acceptor" $ chunkTests runAcceptor , testGroup "Reporter" $ chunkTests runReporterEither ] , testGroup "Char" [ testGroup "Acceptor" [ testGroup "Text" $ charTests @Text runAcceptor , testGroup "ByteString" $ charTests @ByteString runAcceptor ] , testGroup "Reporter" [ testGroup "Text" $ charTests @Text runReporterEither , testGroup "ByteString" $ charTests @ByteString runReporterEither ] ] , testGroup "Reporter specific" reporterTests ] charTests :: forall k p e. (CharParser k p, CharChunk k, Eq e, Show e, Show k) => (forall a. p a -> FilePath -> k -> Either e a) -> [TestTree] charTests run = [ testGroup "CharParser" $ [ testCase "char" $ do ok (char 'a') "abc" 'a' ok (char 'a' <* eof) "a" 'a' err (char 'b') "abc" err (char 'a') "" , testCase "char-random" $ replicateM_ randomTries $ do s <- randomString ok (traverse char s *> eof) s () , testCase "scan" $ do ok (scan (\c -> if c == 'a' then Just c else Nothing)) "abc" 'a' ok (scan (\c -> if c == 'a' then Just c else Nothing) <* eof) "a" 'a' err (scan (\c -> if c == 'b' then Just c else Nothing)) "abc" err (scan (\c -> if c == 'a' then Just c else Nothing)) "" -- because of sentinel err (scan (\c -> if c == '\0' then Just c else Nothing)) "\0" err (scan (\c -> if c == '\0' then Just c else Nothing)) "" , testCase "asciiScan" $ do ok (asciiScan (\c -> if c == asc_a then Just c else Nothing)) "abc" asc_a ok (asciiScan (\c -> if c == asc_a then Just c else Nothing) <* eof) "a" asc_a err (asciiScan (\c -> if c == asc_0 then Just c else Nothing)) "abc" err (asciiScan (\c -> if c == asc_0 then Just c else Nothing)) "" -- because of sentinel err (asciiScan (\c -> if c == 0 then Just c else Nothing)) "\0" err (asciiScan (\c -> if c == 0 then Just c else Nothing)) "" , testCase "asciiByte" $ do ok (asciiByte asc_a) "abc" asc_a ok (asciiByte asc_a <* eof) "a" asc_a ok (asciiByte 127 <* eof) "\x7F" 127 err (asciiByte asc_0) "abc" err (asciiByte asc_0) "" , testCase "asciiByte-random" $ replicateM_ randomTries $ do s <- randomAsciiString ok (traverse (asciiByte . fromIntegral . C.ord) s *> eof) s () ] , testGroup "Char Combinators" [ testCase "satisfy" $ do ok (satisfy (== 'a')) "abc" 'a' ok (satisfy (== 'a') <* eof) "a" 'a' err (satisfy (== 'b')) "abc" err (satisfy (== 'a')) "" -- because of sentinel err (satisfy (== '\0')) "\0" err (satisfy (== '\0')) "" , testCase "satisfy-random" $ replicateM_ randomTries $ do s <- randomString ok (traverse (satisfy . (==)) s *> eof) s () , testCase "asciiSatisfy" $ do ok (asciiSatisfy (== asc_a)) "abc" asc_a ok (asciiSatisfy (== asc_a) <* eof) "a" asc_a err (asciiSatisfy (== asc_0)) "abc" err (asciiSatisfy (== asc_0)) "" -- because of sentinel err (asciiSatisfy (== 0)) "\0" err (asciiSatisfy (== 0)) "" , testCase "asciiSatisfy-random" $ replicateM_ randomTries $ do s <- randomAsciiString ok (traverse (asciiSatisfy . (==) . fromIntegral . C.ord) s *> eof) s () , testCase "string" $ do ok (string "ab") "abc" (stringToChunk "ab") err (string "bc") "abc" err (string "ab") "" , testCase "string-random" $ replicateM_ randomTries $ do s <- randomString ok (string s <* eof) s (stringToChunk s) , testCase "anyChar" $ do ok anyChar "abc" 'a' ok (anyChar <* eof) "a" 'a' err anyChar "" , testCase "anyChar-random" $ replicateM_ randomTries $ do s <- randomString ok (traverse (const anyChar) s *> eof) s () , testCase "anyAsciiByte" $ do ok anyAsciiByte "abc" asc_a ok (anyAsciiByte <* eof) "a" asc_a err anyAsciiByte "" err anyAsciiByte "\x80" , testCase "anyAsciiByte-random" $ replicateM_ randomTries $ do s <- randomAsciiString ok (traverse (const anyAsciiByte) s *> eof) s () , testCase "notChar" $ do ok (notChar 'b') "abc" 'a' ok (notChar 'b' <* eof) "a" 'a' err (notChar 'a') "a" err (notChar 'a') "" , testCase "char'" $ do ok (char' 'a') "a" 'a' ok (char' 'a' <* eof) "a" 'a' ok (char' 'a') "A" 'A' ok (char' 'A') "a" 'a' ok (char' 'A') "A" 'A' ok (char' '9') "9" '9' err (char' 'a') "b" err (char' 'a') "" , testCase "alphaNumChar" $ do ok (alphaNumChar <* eof) "a" 'a' ok alphaNumChar "9" '9' err alphaNumChar "_" err alphaNumChar "" , testCase "digitChar" $ do ok (digitChar 10 <* eof) "9" '9' ok (digitChar 36) "z" 'z' err (digitChar 2) "2" err (digitChar 2) "" , testCase "letterChar" $ do ok (letterChar <* eof) "a" 'a' err letterChar "9" err letterChar "_" err letterChar "" , testCase "lowerChar" $ do ok (lowerChar <* eof) "a" 'a' err lowerChar "A" err lowerChar "9" err lowerChar "_" err lowerChar "" , testCase "upperChar" $ do ok (upperChar <* eof) "A" 'A' err upperChar "a" err upperChar "9" err upperChar "_" err upperChar "" , testCase "symbolChar" $ do ok (symbolChar <* eof) "+" '+' err symbolChar "a" err symbolChar "." err symbolChar "" , testCase "punctuationChar" $ do ok (punctuationChar <* eof) "." '.' err punctuationChar "+" err punctuationChar "a" err punctuationChar "" , testCase "spaceChar" $ do ok (spaceChar <* eof) " " ' ' ok (spaceChar <* eof) "\n" '\n' ok (spaceChar <* eof) "\r" '\r' ok (spaceChar <* eof) "\t" '\t' err spaceChar "a" err spaceChar "" , testCase "asciiChar" $ do ok (asciiChar <* eof) "a" 'a' ok (asciiChar <* eof) "\x7F" '\x7F' err asciiChar "\x80" err asciiChar "" , testCase "categoryChar" $ do ok (categoryChar C.UppercaseLetter <* eof) "A" 'A' err (categoryChar C.UppercaseLetter) "a" err (categoryChar C.UppercaseLetter) "" , testCase "digitByte" $ do ok (digitByte 2 <* eof) "0" asc_0 ok (digitByte 10 <* eof) "9" asc_9 err (digitByte 10) "\0" err (digitByte 10) "" , testCase "skipChars" $ do ok (skipChars 0) "" () ok (skipChars 3 <* eof) "abc" () ok (skipChars 3 <* char 'b') "aaab" () err (skipChars 1) "" err (skipChars 2) "a" , testCase "takeChars" $ do ok (takeChars 0) "" (stringToChunk "") ok (takeChars 3 <* eof) "abc" (stringToChunk "abc") ok (takeChars 3 <* char 'b') "aaab" (stringToChunk "aaa") err (takeChars 1) "" err (takeChars 2) "a" , testCase "skipCharsWhile" $ do ok (skipCharsWhile (== 'a')) "" () ok (skipCharsWhile (== 'a')) "b" () ok (skipCharsWhile (== 'a') *> eof) "aaa" () ok (skipCharsWhile (== 'a') *> char 'b') "aaab" 'b' , testCase "takeCharsWhile" $ do ok (takeCharsWhile (== 'a')) "" (stringToChunk "") ok (takeCharsWhile (== 'a')) "b" (stringToChunk "") ok (takeCharsWhile (== 'a') <* eof) "aaa" (stringToChunk "aaa") ok (takeCharsWhile (== 'a') <* char 'b') "aaab" (stringToChunk "aaa") , testCase "skipCharsWhile1" $ do err (skipCharsWhile1 (== 'a')) "" err (skipCharsWhile1 (== 'a')) "b" ok (skipCharsWhile1 (== 'a') *> eof) "aaa" () ok (skipCharsWhile1 (== 'a') *> char 'b') "aaab" 'b' , testCase "takeCharsWhile1" $ do err (takeCharsWhile1 (== 'a')) "" err (takeCharsWhile1 (== 'a')) "b" ok (takeCharsWhile1 (== 'a') <* eof) "aaa" (stringToChunk "aaa") ok (takeCharsWhile1 (== 'a') <* char 'b') "aaab" (stringToChunk "aaa") ] , testGroup "Fraction Combinators" [ testCase "fractionDec" $ do okFraction (fractionDec (pure ()) <* eof) "1.23" (Right (123, 10, -2)) okFraction (fractionDec (pure ()) <* eof) "99e0" (Right (99, 10, 0)) okFraction (fractionDec (pure ()) <* eof) "123.45" (Right (12345, 10, -2)) okFraction (fractionDec (pure ()) <* eof) "00123." (Right (123, 10, 0)) okFraction (fractionDec (pure ()) <* eof) "456.000" (Right (456000, 10, -3)) okFraction (fractionDec (pure ()) <* eof) "987e-5" (Right (987, 10, -5)) okFraction (fractionDec (pure ()) <* eof) "987.e-123" (Right (987, 10, -123)) okFraction (fractionDec (pure ()) <* eof) "987.654e-67" (Right (987654, 10, -70)) okFraction (fractionDec (pure ()) <* eof) "987.654000e-7" (Right (987654000, 10, -13)) okFraction (fractionDec (pure ()) <* eof) "000987.654000e-7" (Right (987654000, 10, -13)) okFraction (fractionDec (pure ()) <* eof) "987e+5" (Right (987, 10, 5)) okFraction (fractionDec (pure ()) <* eof) "987.e+123" (Right (987, 10, 123)) okFraction (fractionDec (pure ()) <* eof) "987.654e+67" (Right (987654, 10, 64)) okFraction (fractionDec (pure ()) <* eof) "987.654000e+7" (Right (987654000, 10, 1)) okFraction (fractionDec (pure ()) <* eof) "000987.654000e+7" (Right (987654000, 10, 1)) okFraction (fractionDec (pure ()) <* eof) "987e5" (Right (987, 10, 5)) okFraction (fractionDec (pure ()) <* eof) "987.e123" (Right (987, 10, 123)) okFraction (fractionDec (pure ()) <* eof) "987.654e67" (Right (987654, 10, 64)) okFraction (fractionDec (pure ()) <* eof) "987.654000e7" (Right (987654000, 10, 1)) okFraction (fractionDec (pure ()) <* eof) "000987.654000e7" (Right (987654000, 10, 1)) okFraction (fractionDec (pure ())) "123" (Left 123) errFraction (fractionDec (pure ())) "123e" errFraction (fractionDec (pure ())) "" errFraction (fractionDec (pure ())) "abc" ] , testGroup "Integer Combinators" [ testCase "decimal" $ do ok @Integer (decimal <* eof) "0123" 123 ok @Integer (decimal <* eof) "1234567890" 1234567890 ok @Integer (decimal <* string "abc" <* eof) "123abc" 123 err @Integer decimal "abc" err @Integer decimal "-1" err @Integer decimal "" , testCase "octal" $ do ok @Integer (octal <* eof) "0123" 0o123 ok @Integer (octal <* eof) "12345670" 0o12345670 ok @Integer (octal <* string "abc" <* eof) "123abc" 0o123 err @Integer octal "8abc" err @Integer octal "-1" err @Integer octal "" , testCase "hexadecimal" $ do ok @Integer (hexadecimal <* eof) "0123" 0x123 ok @Integer (hexadecimal <* eof) "123456789aBcDeF0" 0x123456789ABCDEF0 ok @Integer (hexadecimal <* string "xyz" <* eof) "123xyz" 0x123 err @Integer hexadecimal "gabc" err @Integer hexadecimal "-1" err @Integer hexadecimal "" , testCase "integer" $ do ok @Integer (integer (char '_') 10) "1_2_3" 123 ok @Integer (integer (char '_') 10 <* char '_') "1_2_3_" 123 ok @Integer (integer (optional $ char '_') 10) "123_456_789" 123456789 err @Integer (integer (pure ()) 10) "abc" err @Integer (integer (char '_') 10) "_1_2_3" err @Integer (integer (pure ()) 10) "-1" err @Integer (integer (pure ()) 10) "" ok @Integer (integer (pure ()) 2) "101" 5 ok @Integer (integer (pure ()) 7) "321" 162 ok @Integer (integer (pure ()) 36) "XyZ" 44027 , testCase "integer'" $ do ok @(Integer, Int) (integer' (char '_') 10) "1_2_3" (123, 3) ok @(Integer, Int) (integer' (char '_') 10 <* char '_') "1_2_3_" (123, 3) ok @(Integer, Int) (integer' (optional $ char '_') 10) "123_456_789" (123456789, 9) err @(Integer, Int) (integer' (pure ()) 10) "abc" err @(Integer, Int) (integer' (char '_') 10) "_1_2_3" err @(Integer, Int) (integer' (pure ()) 10) "-1" err @(Integer, Int) (integer' (pure ()) 10) "" ok @(Integer, Int) (integer' (pure ()) 10) "0123" (123, 4) ok @(Integer, Int) (integer' (pure ()) 10) "01230" (1230, 5) ok @(Integer, Int) (integer' (pure ()) 10) "000" (0, 3) ok @(Integer, Int) (integer' (pure ()) 2) "101" (5, 3) ok @(Integer, Int) (integer' (pure ()) 7) "321" (162, 3) ok @(Integer, Int) (integer' (pure ()) 36) "XyZ" (44027, 3) , testCase "signed" $ do ok @Integer (signed decimal) "-123" (-123) ok @Integer (signed decimal) "+123" 123 err @Integer (signed decimal) "- 123" err @Integer (signed decimal) "+ 123" err @Integer (signed decimal) "" , testCase "digit" $ do ok (digit 2) "1" 1 ok (digit 10) "7" 7 ok (digit 36) "Z" 35 err (digit 10) "a" err (digit 10) "" ] ] where ok :: (Eq a, Show a, HasCallStack) => p a -> String -> a -> Assertion ok p i o = run p "filename" (stringToChunk @k i) @?= Right o err :: (Eq a, Show a, HasCallStack) => p a -> String -> Assertion err p i = assertBool "err" $ isLeft $ run p "filename" (stringToChunk @k i) errFraction :: HasCallStack => p (Either Integer (Integer, Int, Integer)) -> String -> Assertion errFraction = err okFraction :: HasCallStack => p (Either Integer (Integer, Int, Integer)) -> String -> Either Integer (Integer, Int, Integer) -> Assertion okFraction = ok chunkTests :: forall p e. (ChunkParser Text p, Eq e, Show e) => (forall a. p a -> FilePath -> Text -> Either e a) -> [TestTree] chunkTests run = [ testGroup "ChunkParser" [ testCase "getFile" $ do ok getFile "" "filename" , testCase "getPos" $ do ok getPos "" (Pos 1 1) ok (element 'a' *> getPos) "abc" (Pos 1 2) ok (element 'a' *> element '\n' *> getPos) "a\nb" (Pos 2 1) ok (chunk "a\n" *> getPos) "a\nb" (Pos 1 3) -- chunk must not contain newlines! ok (element 'a' *> element '\n' *> element 'b' *> getPos) "a\nb" (Pos 2 2) , testCase "getRefPos" $ do ok getRefPos "" (Pos 1 1) ok (element 'a' *> getRefPos) "abc" (Pos 1 1) ok (element 'a' *> element '\n' *> withRefPos getRefPos) "a\nb" (Pos 2 1) ok (element 'a' *> element '\n' *> element 'b' *> withRefPos getRefPos) "a\nb" (Pos 2 2) ok (element 'a' *> element '\n' *> withRefPos (element 'b' *> getRefPos)) "a\nb" (Pos 2 1) , testCase "notFollowedBy" $ do ok (element 'a' <* notFollowedBy (element 'c')) "abc" 'a' err (element 'a' <* notFollowedBy (element 'b')) "abc" ok (element 'a' *> notFollowedBy (chunk "bd") *> element 'b') "abc" 'b' err (element 'a' *> notFollowedBy (chunk "bc")) "abc" ok (element 'a' *> notFollowedBy (element 'c') *> getPos) "abc" (Pos 1 2) , testCase "lookAhead" $ do ok (lookAhead (element 'a')) "abc" 'a' ok (lookAhead (element 'a') *> getPos) "abc" (Pos 1 1) ok (lookAhead (chunk "ab") *> getPos) "abc" (Pos 1 1) err (lookAhead (element 'b')) "abc" err (lookAhead (chunk "bd")) "abc" err (lookAhead (element 'a')) "" , testCase "failWith" $ err (failWith (ECombinator "empty") :: p ()) "abc" , testCase "eof" $ do ok eof "" () ok (chunk "abc" *> eof) "abc" () err eof "abc" err eof "\0" err (chunk "ab" *> eof) "abc" , testCase "label" $ do ok (label "blub" $ element 'a') "abc" 'a' err (label "blub" $ element 'b') "abc" ok (element 'a' "blub") "abc" 'a' , testCase "hidden" $ do ok (hidden $ element 'a') "abc" 'a' err (hidden $ element 'b') "abc" , testCase "commit" $ do ok (commit $ element 'a') "abc" 'a' err (commit $ element 'b') "abc" , testCase "recover" $ do ok (recover (element 'a' <* eof) (element 'b')) "a" 'a' err (recover (element 'a') (element 'b')) "c" err (recover (element 'a' <* eof) (element 'b')) "c" , testCase "element" $ do ok (element 'a') "abc" 'a' ok (element 'a' <* eof) "a" 'a' err (element 'b') "abc" err (element 'a') "" -- sentinel ok (element '\0') "\0" '\0' ok (element '\0' <* eof) "\0" '\0' err (element '\0') "" , testCase "elementScan" $ do ok (elementScan (\c -> if c == 'a' then Just c else Nothing)) "abc" 'a' ok (elementScan (\c -> if c == 'a' then Just c else Nothing) <* eof) "a" 'a' err (elementScan (\c -> if c == 'b' then Just c else Nothing)) "abc" err (elementScan Just) "" -- sentinel ok (elementScan (\c -> if c == '\0' then Just c else Nothing)) "\0" '\0' ok (elementScan (\c -> if c == '\0' then Just c else Nothing) <* eof) "\0" '\0' , testCase "chunk" $ do ok (chunk "ab") "abc" "ab" err (chunk "bc") "abc" err (chunk "ab") "" , testCase "asChunk" $ do ok (asChunk (void $ chunk "ab")) "abc" "ab" ok (asChunk (void $ anyElement *> anyElement)) "abc" "ab" ok (asChunk (skipCount 2 anyElement)) "abc" "ab" err (asChunk (void $ chunk "bc")) "abc" err (asChunk (void $ chunk "ab")) "" ] , testGroup "Position Combinators" [ testCase "getLine" $ do ok getLine "" 1 ok (element 'a' *> getLine) "abc" 1 ok (element 'a' *> element '\n' *> getLine) "a\nb" 2 ok (element 'a' *> element '\n' *> element 'b' *> getLine) "a\nb" 2 , testCase "getRefLine" $ do ok getRefLine "" 1 ok (element 'a' *> getRefLine) "abc" 1 ok (element 'a' *> element '\n' *> withRefPos getRefLine) "a\nb" 2 ok (element 'a' *> element '\n' *> element 'b' *> withRefPos getRefLine) "a\nb" 2 ok (element 'a' *> element '\n' *> withRefPos (element 'b' *> getRefLine)) "a\nb" 2 , testCase "getColumn" $ do ok getColumn "" 1 ok (element 'a' *> getColumn) "abc" 2 ok (element 'a' *> element '\n' *> getColumn) "a\nb" 1 ok (element 'a' *> element '\n' *> element 'b' *> getColumn) "a\nb" 2 , testCase "getRefColumn" $ do ok getRefColumn "" 1 ok (element 'a' *> getRefColumn) "abc" 1 ok (element 'a' *> element '\n' *> withRefPos getRefColumn) "a\nb" 1 ok (element 'a' *> element '\n' *> element 'b' *> withRefPos getRefColumn) "a\nb" 2 ok (element 'a' *> element '\n' *> withRefPos (element 'b' *> getRefColumn)) "a\nb" 1 , testCase "withPos" $ do ok (withPos $ element 'a') "abc" (Pos 1 1, 'a') ok (element 'a' *> withPos (element 'b')) "abc" (Pos 1 2, 'b') ok (element 'a' *> element '\n' *> withPos (element 'b')) "a\nb" (Pos 2 1, 'b') , testCase "withSpan" $ do ok (withSpan $ chunk "ab") "abc" ((Pos 1 1, Pos 1 3), "ab") ok (element 'a' *> withSpan (chunk "bcd")) "abcde" ((Pos 1 2, Pos 1 5), "bcd") ok (element 'a' *> element '\n' *> withSpan (chunk "bcd")) "a\nbcde" ((Pos 2 1, Pos 2 4), "bcd") ] , testGroup "Element Combinators" [ testCase "elementSatisfy" $ do ok (elementSatisfy (== 'a')) "abc" 'a' ok (elementSatisfy (== 'a') <* eof) "a" 'a' err (elementSatisfy (== 'b')) "abc" err (elementSatisfy (== 'a')) "" -- sentinel ok (elementSatisfy (== '\0')) "\0" '\0' ok (elementSatisfy (== '\0') <* eof) "\0" '\0' err (elementSatisfy (== '\0')) "" , testCase "anyElement" $ do ok anyElement "abc" 'a' err anyElement "" , testCase "notElement" $ do ok (notElement 'b') "abc" 'a' err (notElement 'a') "a" err (notElement 'a') "" , testCase "takeElementsWhile" $ do ok (takeElementsWhile (== 'a')) "" "" ok (takeElementsWhile (== 'a')) "b" "" ok (takeElementsWhile (== 'a') <* eof) "aaa" "aaa" ok (takeElementsWhile (== 'a') <* element 'b') "aaab" "aaa" , testCase "skipElements" $ do ok (skipElements 0) "" () ok (skipElements 3 <* eof) "abc" () ok (skipElements 3 <* element 'b') "aaab" () err (skipElements 1) "" err (skipElements 2) "a" , testCase "takeElements" $ do ok (takeElements 0) "" "" ok (takeElements 3 <* eof) "abc" "abc" ok (takeElements 3 <* element 'b') "aaab" "aaa" err (takeElements 1) "" err (takeElements 2) "a" , testCase "skipElementsWhile" $ do ok (skipElementsWhile (== 'a')) "" () ok (skipElementsWhile (== 'a')) "b" () ok (skipElementsWhile (== 'a') *> eof) "aaa" () ok (skipElementsWhile (== 'a') *> element 'b') "aaab" 'b' , testCase "skipElementsWhile1" $ do err (skipElementsWhile1 (== 'a')) "" err (skipElementsWhile1 (== 'a')) "b" ok (skipElementsWhile1 (== 'a') *> eof) "aaa" () ok (skipElementsWhile1 (== 'a') *> element 'b') "aaab" 'b' , testCase "takeElementsWhile1" $ do err (takeElementsWhile1 (== 'a')) "" err (takeElementsWhile1 (== 'a')) "b" ok (takeElementsWhile1 (== 'a') <* eof) "aaa" "aaa" ok (takeElementsWhile1 (== 'a') <* element 'b') "aaab" "aaa" ] , testGroup "MonadFail" [ testCase "fail" $ do err (failWith (ECombinator "empty") :: p ()) "abc" ] , testGroup "Alternative" [ testCase "(<|>)" $ do ok (element 'b' <|> element 'a' <|> element 'c') "abc" 'a' ok (chunk "abd" <|> chunk "abc" <|> chunk "abe") "abcdef" "abc" , testCase "empty" $ do err (empty :: p ()) "abc" err (empty :: p ()) "" ] , testGroup "Basic Combinators" [ testCase "optional" $ do ok (optional (element 'a')) "abc" (Just 'a') ok (optional (element 'b')) "abc" Nothing ok (optional (element 'a')) "" Nothing , testCase "some" $ do ok (some (element 'a')) "abc" (NE.fromList "a") ok (some (element 'a')) "aabc" (NE.fromList "aa") err (some (element 'b')) "abc" err (some (element 'a')) "" , testCase "many" $ do ok (many (element 'a')) "abc" "a" ok (many (element 'a')) "aabc" "aa" ok (many (element 'b')) "abc" "" ok (many (element 'a')) "" "" ] ] where ok :: (Eq a, Show a, HasCallStack) => p a -> Text -> a -> Assertion ok p i o = run p "filename" i @?= Right o err :: (Eq a, Show a, HasCallStack) => p a -> Text -> Assertion err p i = assertBool "err" $ isLeft $ run p "filename" i reporterTests :: [TestTree] reporterTests = [ testCase "recover" $ do run (element 'a' <* eof) "a" (Just 'a', 0) run (element 'a') "b" (Nothing, 1) run (recover (element 'a' <* eof) (element 'b')) "a" (Just 'a', 0) run (recover (element 'a') (element 'b')) "b" (Just 'b', 1) run ((,) <$> recover (element 'a') (element 'b') <*> recover (element 'a') (element 'c')) "bc" (Just ('b', 'c'), 2) run (recover (element 'a') (element 'b') *> recover (element 'a') (element 'c') *> element 'a') "bcd" (Nothing, 3) run (recover (element 'a' <* eof) (element 'b')) "c" (Nothing, 1) ] where run :: (Eq a, Show a, HasCallStack) => Reporter Text a -> Text -> (Maybe a, Int) -> Assertion run p i (o, r) = do let (out, rep) = runReporter p "filename" i out @?= o length rep @?= r {- TODO: Instances: Semigroup Monoid Functor Applicative Monad MonadPlus Alternative Additional combinators provided by parser-combinators. They are also tested by megaparsec: endBy1 someTill sepBy1 sepEndBy1 between choice count count' eitherP endBy manyTill option sepBy sepEndBy skipMany skipSome skipCount skipManyTill skipSomeTill Identation: align indented line linefold Fraction: fraction fractionHex Element Combinators: scanElements scanElements1 Char Combinators: scanChars scanChars1 -}